Skip to content

Commit 6189a7c

Browse files
authored
perf(crypto): CRP-1559 Use affine rather than projective points for BLS12-381 signture protocols (#7969)
The `multi_sig` and `threshold_sig` BLS12-381 signature libraries used projective points for public keys and signatures. Various operations such as serialization or verifying a BLS signature require that the input points are in affine format, and converting from projective to affine is relatively expensive. Other operations like point addition are cheaper if one of the elements is in affine form. Also when deserializing a point the natural encoding is affine, not projective. So for example when verifying a multi signature, the signature would be presented to the crypto component as bytes, deserialized into projective form, then converted back to affine in order to verify the signature equation. Serialization and deserialization of the relevant artifacts is unaffected. Projective points cannot be serialized directly, instead they must anyway be converted into affine form, which right now happens internally in `G(1|2)Projective::serialize`. Likewise, deserialization is always into affine coordinates, and then the point is converted to projective. This removes serialization and deserialization functions from the projective types, as potential performance footguns. For serialization, that's because the serialization hides the (expensive) conversion to affine form. For deserialization, it's because keeping the point in affine form as long as possible is typically best for performance.
1 parent f34ab00 commit 6189a7c

File tree

19 files changed

+180
-217
lines changed

19 files changed

+180
-217
lines changed

rs/crypto/internal/crypto_lib/bls12_381/type/benches/ops.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ fn bls12_381_scalar_ops(c: &mut Criterion) {
206206
group.bench_function("serialize", |b| {
207207
b.iter_batched_ref(
208208
|| random_scalar(rng),
209-
|pt| pt.serialize(),
209+
|s| s.serialize(),
210210
BatchSize::SmallInput,
211211
)
212212
});
@@ -275,24 +275,24 @@ fn bls12_381_g1_ops(c: &mut Criterion) {
275275

276276
group.bench_function("serialize", |b| {
277277
b.iter_batched_ref(
278-
|| random_g1(rng),
278+
|| random_g1(rng).to_affine(),
279279
|pt| pt.serialize(),
280280
BatchSize::SmallInput,
281281
)
282282
});
283283

284284
group.bench_function("deserialize", |b| {
285285
b.iter_batched_ref(
286-
|| random_g1(rng).serialize(),
287-
|bytes| G1Projective::deserialize(bytes),
286+
|| random_g1(rng).to_affine().serialize(),
287+
|bytes| G1Affine::deserialize(bytes),
288288
BatchSize::SmallInput,
289289
)
290290
});
291291

292292
group.bench_function("deserialize_unchecked", |b| {
293293
b.iter_batched_ref(
294-
|| random_g1(rng).serialize(),
295-
|bytes| G1Projective::deserialize_unchecked(bytes),
294+
|| random_g1(rng).to_affine().serialize(),
295+
|bytes| G1Affine::deserialize_unchecked(bytes),
296296
BatchSize::SmallInput,
297297
)
298298
});
@@ -475,39 +475,43 @@ fn bls12_381_g2_ops(c: &mut Criterion) {
475475

476476
group.bench_function("serialize", |b| {
477477
b.iter_batched_ref(
478-
|| random_g2(rng),
478+
|| random_g2(rng).to_affine(),
479479
|pt| pt.serialize(),
480480
BatchSize::SmallInput,
481481
)
482482
});
483483

484484
group.bench_function("deserialize", |b| {
485485
b.iter_batched_ref(
486-
|| random_g2(rng).serialize(),
487-
|bytes| G2Projective::deserialize(bytes),
486+
|| random_g2(rng).to_affine().serialize(),
487+
|bytes| G2Affine::deserialize(bytes),
488488
BatchSize::SmallInput,
489489
)
490490
});
491491

492492
group.bench_function("deserialize_unchecked", |b| {
493493
b.iter_batched_ref(
494-
|| random_g2(rng).serialize(),
495-
|bytes| G2Projective::deserialize_unchecked(bytes),
494+
|| random_g2(rng).to_affine().serialize(),
495+
|bytes| G2Affine::deserialize_unchecked(bytes),
496496
BatchSize::SmallInput,
497497
)
498498
});
499499

500500
group.bench_function("deserialize_cached", |b| {
501501
b.iter_batched_ref(
502-
|| (G2Affine::generator() * Scalar::from_u32(rng.r#gen::<u32>() % 100)).serialize(),
502+
|| {
503+
(G2Affine::generator() * Scalar::from_u32(rng.r#gen::<u32>() % 100))
504+
.to_affine()
505+
.serialize()
506+
},
503507
|bytes| G2Affine::deserialize_cached(bytes),
504508
BatchSize::SmallInput,
505509
)
506510
});
507511

508512
group.bench_function("deserialize_cached_miss", |b| {
509513
b.iter_batched_ref(
510-
|| random_g2(rng).serialize(),
514+
|| random_g2(rng).to_affine().serialize(),
511515
|bytes| G2Affine::deserialize_cached(bytes),
512516
BatchSize::SmallInput,
513517
)

rs/crypto/internal/crypto_lib/bls12_381/type/src/lib.rs

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,15 @@ macro_rules! define_affine_and_projective_types {
13021302
let v = scalars.clone().map(|s| self * s);
13031303
$projective::batch_normalize_array(&v)
13041304
}
1305+
1306+
/// Sum some points
1307+
pub fn sum(pts: &[Self]) -> $projective {
1308+
let mut sum = ic_bls12_381::$projective::identity();
1309+
for pt in pts {
1310+
sum += pt.inner();
1311+
}
1312+
$projective::new(sum)
1313+
}
13051314
}
13061315

13071316
paste! {
@@ -1365,33 +1374,12 @@ macro_rules! define_affine_and_projective_types {
13651374
Self::new(sum)
13661375
}
13671376

1368-
/// Deserialize a point (compressed format only)
1369-
///
1370-
/// This version verifies that the decoded point is within the prime order
1371-
/// subgroup, and is safe to call on untrusted inputs.
1372-
pub fn deserialize<B: AsRef<[u8]>>(bytes: &B) -> Result<Self, PairingInvalidPoint> {
1373-
let pt = $affine::deserialize(bytes)?;
1374-
Ok(pt.into())
1375-
}
1376-
1377-
/// Serialize a point in compressed format in some specific type
1378-
pub fn serialize_to<T: From<[u8; Self::BYTES]>>(&self) -> T {
1379-
T::from(self.serialize())
1380-
}
1381-
1382-
/// Deserialize a point (compressed format only), trusted bytes edition
1383-
///
1384-
/// As only compressed format is accepted, it is not possible to
1385-
/// create a point which is not on the curve. However it is possible
1386-
/// using this function to create a point which is not within the
1387-
/// prime-order subgroup. This can be detected by calling is_torsion_free
1388-
pub fn deserialize_unchecked<B: AsRef<[u8]>>(bytes: &B) -> Result<Self, PairingInvalidPoint> {
1389-
let pt = $affine::deserialize_unchecked(bytes)?;
1390-
Ok(pt.into())
1391-
}
1392-
13931377
/// Serialize this point in compressed format
1394-
pub fn serialize(&self) -> [u8; Self::BYTES] {
1378+
///
1379+
/// This exists for implementing Debug but is intentionally pub(crate)
1380+
/// since it hides the expensive affine conversion so should be avoided
1381+
/// in production code.
1382+
pub(crate) fn serialize(&self) -> [u8; Self::BYTES] {
13951383
$affine::from(self).serialize()
13961384
}
13971385

rs/crypto/internal/crypto_lib/bls12_381/type/tests/tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,14 +1714,14 @@ test_point_operation!(serialization_round_trip, [g1, g2], {
17141714
let rng = &mut reproducible_rng();
17151715

17161716
for _ in 1..30 {
1717-
let orig = Projective::hash(b"serialization-round-trip-test", &rng.r#gen::<[u8; 32]>());
1717+
let orig = Affine::hash(b"serialization-round-trip-test", &rng.r#gen::<[u8; 32]>());
17181718
let bits = orig.serialize();
17191719

1720-
let d = Projective::deserialize(&bits).expect("Invalid serialization");
1720+
let d = Affine::deserialize(&bits).expect("Invalid serialization");
17211721
assert_eq!(orig, d);
17221722
assert_eq!(d.serialize(), bits);
17231723

1724-
let du = Projective::deserialize_unchecked(&bits).expect("Invalid serialization");
1724+
let du = Affine::deserialize_unchecked(&bits).expect("Invalid serialization");
17251725
assert_eq!(orig, du);
17261726
assert_eq!(du.serialize(), bits);
17271727
}

rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl DerivationContext {
6161
DERIVATION_CANISTER_DST,
6262
);
6363

64-
let canister_key = G2Affine::generator() * &offset + master_pk;
64+
let canister_key = G2Affine::from(G2Affine::generator() * &offset + master_pk);
6565

6666
if let Some(context) = &self.context {
6767
let context_offset =
@@ -70,7 +70,7 @@ impl DerivationContext {
7070
offset += context_offset;
7171
(G2Affine::from(canister_key_with_context), offset)
7272
} else {
73-
(G2Affine::from(canister_key), offset)
73+
(canister_key, offset)
7474
}
7575
}
7676
}

rs/crypto/internal/crypto_lib/multi_sig/bls12_381/src/api.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,13 @@ pub fn combine(
9393

9494
fn key_from_bytes_with_cache(public_key_bytes: &PublicKeyBytes) -> Result<PublicKey, CryptoError> {
9595
// This can't be defined on PublicKey because it is just a typedef for G2Projective at the moment
96-
ic_crypto_internal_bls12_381_type::G2Affine::deserialize_cached(&public_key_bytes.0)
97-
.map_err(|_| CryptoError::MalformedPublicKey {
96+
ic_crypto_internal_bls12_381_type::G2Affine::deserialize_cached(&public_key_bytes.0).map_err(
97+
|_| CryptoError::MalformedPublicKey {
9898
algorithm: AlgorithmId::MultiBls12_381,
9999
key_bytes: Some(public_key_bytes.0.to_vec()),
100100
internal_error: "Point decoding failed".to_string(),
101-
})
102-
.map(|pt| pt.into())
101+
},
102+
)
103103
}
104104

105105
/// Verifies an individual signature over the given `message` using the given

rs/crypto/internal/crypto_lib/multi_sig/bls12_381/src/crypto.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::types::{
77
};
88

99
use ic_crypto_internal_bls12_381_type::{
10-
G1Projective, G2Affine, G2Projective, Scalar, verify_bls_signature,
10+
G1Affine, G1Projective, G2Affine, Scalar, verify_bls_signature,
1111
};
1212

1313
use ic_crypto_sha2::DomainSeparationContext;
@@ -56,12 +56,12 @@ pub fn keypair_from_seed(seed: [u64; 4]) -> (SecretKey, PublicKey) {
5656

5757
pub fn keypair_from_rng<R: Rng + CryptoRng>(rng: &mut R) -> (SecretKey, PublicKey) {
5858
let secret_key = Scalar::random(rng);
59-
let public_key = G2Affine::generator() * &secret_key;
59+
let public_key = (G2Affine::generator() * &secret_key).to_affine();
6060
(secret_key, public_key)
6161
}
6262

6363
pub fn sign_point(point: &G1Projective, secret_key: &SecretKey) -> IndividualSignature {
64-
point * secret_key
64+
(point * secret_key).to_affine()
6565
}
6666
pub fn sign_message(message: &[u8], secret_key: &SecretKey) -> IndividualSignature {
6767
sign_point(&hash_message_to_g1(message), secret_key)
@@ -80,22 +80,23 @@ pub fn create_pop(public_key: &PublicKey, secret_key: &SecretKey) -> Pop {
8080
}
8181

8282
pub fn combine_signatures(signatures: &[IndividualSignature]) -> CombinedSignature {
83-
G1Projective::sum(signatures)
83+
G1Affine::sum(signatures).to_affine()
8484
}
8585
pub fn combine_public_keys(public_keys: &[PublicKey]) -> CombinedPublicKey {
86-
G2Projective::sum(public_keys)
86+
G2Affine::sum(public_keys).to_affine()
8787
}
8888

89-
pub fn verify_point(hash: &G1Projective, signature: &G1Projective, public_key: &PublicKey) -> bool {
90-
verify_bls_signature(&signature.into(), &public_key.into(), &hash.into())
89+
pub fn verify_point(hash: &G1Affine, signature: &G1Affine, public_key: &PublicKey) -> bool {
90+
verify_bls_signature(signature, public_key, hash)
9191
}
92+
9293
pub fn verify_individual_message_signature(
9394
message: &[u8],
9495
signature: &IndividualSignature,
9596
public_key: &PublicKey,
9697
) -> bool {
9798
let hash = hash_message_to_g1(message);
98-
verify_point(&hash, signature, public_key)
99+
verify_point(&hash.to_affine(), signature, public_key)
99100
}
100101
pub fn verify_pop(pop: &Pop, public_key: &PublicKey) -> bool {
101102
let public_key_bytes = PublicKeyBytes::from(public_key);
@@ -104,7 +105,7 @@ pub fn verify_pop(pop: &Pop, public_key: &PublicKey) -> bool {
104105
.extend(DomainSeparationContext::new(DOMAIN_MULTI_SIG_BLS12_381_POP).as_bytes());
105106
domain_separated_public_key.extend(&public_key_bytes.0[..]);
106107
let hash = hash_public_key_to_g1(&domain_separated_public_key);
107-
verify_point(&hash, pop, public_key)
108+
verify_point(&hash.to_affine(), pop, public_key)
108109
}
109110

110111
pub fn verify_combined_message_signature(
@@ -114,5 +115,5 @@ pub fn verify_combined_message_signature(
114115
) -> bool {
115116
let hash = hash_message_to_g1(message);
116117
let public_key = combine_public_keys(public_keys);
117-
verify_point(&hash, signature, &public_key)
118+
verify_point(&hash.to_affine(), signature, &public_key)
118119
}

rs/crypto/internal/crypto_lib/multi_sig/bls12_381/src/tests.rs

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,8 @@ use crate::{
55
types::IndividualSignature, types::PublicKey, types::SecretKey, types::SecretKeyBytes,
66
types::arbitrary,
77
};
8-
use ic_crypto_internal_bls12_381_type::G1Projective;
98
use ic_crypto_test_utils_reproducible_rng::reproducible_rng;
109

11-
fn check_single_point_signature_verifies(
12-
secret_key: &SecretKey,
13-
public_key: &PublicKey,
14-
point: &G1Projective,
15-
) {
16-
let signature = multi_crypto::sign_point(point, secret_key);
17-
assert!(multi_crypto::verify_point(point, &signature, public_key));
18-
}
19-
20-
fn check_individual_multi_signature_contribution_verifies(
21-
secret_key: &SecretKey,
22-
public_key: &PublicKey,
23-
message: &[u8],
24-
) {
25-
let signature = multi_crypto::sign_message(message, secret_key);
26-
assert!(multi_crypto::verify_individual_message_signature(
27-
message, &signature, public_key
28-
));
29-
}
30-
3110
fn check_multi_signature_verifies(keys: &[(SecretKey, PublicKey)], message: &[u8]) {
3211
let signatures: Vec<IndividualSignature> = keys
3312
.iter()
@@ -56,7 +35,11 @@ mod stability {
5635
#[test]
5736
fn message_to_g1() {
5837
assert_eq!(
59-
hex::encode(multi_crypto::hash_message_to_g1(b"abc").serialize()),
38+
hex::encode(
39+
multi_crypto::hash_message_to_g1(b"abc")
40+
.to_affine()
41+
.serialize()
42+
),
6043
"a13964470939e806ca5ca96b348ab13af3f06a7d9dc4e8a0cf20d8a81a6d8f5a692c67424228d45d749e7832d27cea79"
6144
);
6245
}
@@ -66,7 +49,11 @@ mod stability {
6649
let (_secret_key, public_key) = multi_crypto::keypair_from_rng(&mut csprng);
6750
let public_key_bytes = PublicKeyBytes::from(&public_key);
6851
assert_eq!(
69-
hex::encode(multi_crypto::hash_public_key_to_g1(&public_key_bytes.0[..]).serialize()),
52+
hex::encode(
53+
multi_crypto::hash_public_key_to_g1(&public_key_bytes.0[..])
54+
.to_affine()
55+
.serialize()
56+
),
7057
"b02fd0d54faab7498924d7e230f84b00519ea7f3846cd30f82b149c1f172ad79ee68adb2ea2fc8a2d40ffdf3fd5df02a"
7158
);
7259
}
@@ -125,8 +112,9 @@ mod basic_functionality {
125112
let number_of_messages = 100;
126113
let points: HashSet<_> = (0..number_of_messages as u32)
127114
.map(|number| {
128-
let g1 = multi_crypto::hash_message_to_g1(&number.to_be_bytes()[..]);
129-
let bytes = g1.serialize();
115+
let bytes = multi_crypto::hash_message_to_g1(&number.to_be_bytes()[..])
116+
.to_affine()
117+
.serialize();
130118
// It suffices to prove that the first 32 bytes are distinct. More requires a
131119
// custom hash implementation.
132120
let mut hashable = [0u8; 32];
@@ -146,7 +134,7 @@ mod basic_functionality {
146134
.map(|_| {
147135
let (_secret_key, public_key) = multi_crypto::keypair_from_rng(rng);
148136
let public_key_bytes = PublicKeyBytes::from(&public_key);
149-
let g1 = multi_crypto::hash_public_key_to_g1(&public_key_bytes.0[..]);
137+
let g1 = multi_crypto::hash_public_key_to_g1(&public_key_bytes.0[..]).to_affine();
150138
let bytes = g1.serialize();
151139
// It suffices to prove that the first 32 bytes are distinct. More requires a
152140
// custom hash implementation.
@@ -177,14 +165,26 @@ mod advanced_functionality {
177165
fn single_point_signature_verifies() {
178166
let (secret_key, public_key) = multi_crypto::keypair_from_seed([1, 2, 3, 4]);
179167
let point = multi_crypto::hash_message_to_g1(b"abba");
180-
check_single_point_signature_verifies(&secret_key, &public_key, &point);
168+
let signature = multi_crypto::sign_point(&point, &secret_key);
169+
assert!(multi_crypto::verify_point(
170+
&point.to_affine(),
171+
&signature,
172+
&public_key
173+
));
181174
}
182175

183176
#[test]
184177
fn individual_multi_signature_contribution_verifies() {
185178
let (secret_key, public_key) = multi_crypto::keypair_from_seed([1, 2, 3, 4]);
186-
check_individual_multi_signature_contribution_verifies(&secret_key, &public_key, b"abba");
179+
let message = b"bjork";
180+
let signature = multi_crypto::sign_message(message, &secret_key);
181+
assert!(multi_crypto::verify_individual_message_signature(
182+
message,
183+
&signature,
184+
&public_key
185+
));
187186
}
187+
188188
#[test]
189189
fn pop_verifies() {
190190
let (secret_key, public_key) = multi_crypto::keypair_from_seed([1, 2, 3, 4]);

0 commit comments

Comments
 (0)