authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2022-07-08 13:21:37+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-07-08 13:21:37+02:00
log6f0807f50f4e946bb850e746beaa5d6556cf7750
tree9f28b287c50cd934452198bfddacdff3fbd07908
parent33a39c4b2b8a597c7d40d0c3ea0f5ca12a4636a8
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

crypto.sign.ed25519: add support for blind key signatures (#11868)

Key blinding allows public keys to be augmented with a secret scalar, making multiple signatures from the same signer unlinkable. https://datatracker.ietf.org/doc/draft-dew-cfrg-signature-key-blinding/ This is required by privacy-preserving applications such as Tor onion services and the PrivacyPass protocol.

1 files changed, 114 insertions(+), 0 deletions(-)

lib/std/crypto/25519/ed25519.zig+114
......@@ -28,6 +28,9 @@ pub const Ed25519 = struct {
2828 /// Length (in bytes) of optional random bytes, for non-deterministic signatures.
2929 pub const noise_length = 32;
3030
31 const CompressedScalar = Curve.scalar.CompressedScalar;
32 const Scalar = Curve.scalar.Scalar;
33
3134 /// An Ed25519 key pair.
3235 pub const KeyPair = struct {
3336 /// Public part.
......@@ -207,6 +210,94 @@ pub const Ed25519 = struct {
207210 return error.SignatureVerificationFailed;
208211 } else |_| {}
209212 }
213
214 /// Ed25519 signatures with key blinding.
215 pub const BlindKeySignatures = struct {
216 /// Length (in bytes) of a blinding seed.
217 pub const blind_seed_length = 32;
218
219 /// A blind secret key.
220 pub const BlindSecretKey = struct {
221 prefix: [64]u8,
222 blind_scalar: CompressedScalar,
223 blind_public_key: CompressedScalar,
224 };
225
226 /// A blind key pair.
227 pub const BlindKeyPair = struct {
228 blind_public_key: [public_length]u8,
229 blind_secret_key: BlindSecretKey,
230 };
231
232 /// Blind an existing key pair with a blinding seed.
233 pub fn blind(key_pair: Ed25519.KeyPair, blind_seed: [blind_seed_length]u8) !BlindKeyPair {
234 var h: [Sha512.digest_length]u8 = undefined;
235 Sha512.hash(key_pair.secret_key[0..32], &h, .{});
236 Curve.scalar.clamp(h[0..32]);
237 const scalar = Curve.scalar.reduce(h[0..32].*);
238
239 var blind_h: [Sha512.digest_length]u8 = undefined;
240 Sha512.hash(blind_seed[0..], &blind_h, .{});
241 const blind_factor = Curve.scalar.reduce(blind_h[0..32].*);
242
243 const blind_scalar = Curve.scalar.mul(scalar, blind_factor);
244 const blind_public_key = (Curve.basePoint.mul(blind_scalar) catch return error.IdentityElement).toBytes();
245
246 var prefix: [64]u8 = undefined;
247 mem.copy(u8, prefix[0..32], h[32..64]);
248 mem.copy(u8, prefix[32..64], blind_h[32..64]);
249
250 const blind_secret_key = .{
251 .prefix = prefix,
252 .blind_scalar = blind_scalar,
253 .blind_public_key = blind_public_key,
254 };
255 return BlindKeyPair{
256 .blind_public_key = blind_public_key,
257 .blind_secret_key = blind_secret_key,
258 };
259 }
260
261 /// Recover a public key from a blind version of it.
262 pub fn unblindPublicKey(blind_public_key: [public_length]u8, blind_seed: [blind_seed_length]u8) ![public_length]u8 {
263 var blind_h: [Sha512.digest_length]u8 = undefined;
264 Sha512.hash(&blind_seed, &blind_h, .{});
265 const inv_blind_factor = Scalar.fromBytes(blind_h[0..32].*).invert().toBytes();
266 const public_key = try (try Curve.fromBytes(blind_public_key)).mul(inv_blind_factor);
267 return public_key.toBytes();
268 }
269
270 /// Sign a message using a blind key pair, and optional random noise.
271 /// Having noise creates non-standard, non-deterministic signatures,
272 /// but has been proven to increase resilience against fault attacks.
273 pub fn sign(msg: []const u8, key_pair: BlindKeyPair, noise: ?[noise_length]u8) ![signature_length]u8 {
274 var h = Sha512.init(.{});
275 if (noise) |*z| {
276 h.update(z);
277 }
278 h.update(&key_pair.blind_secret_key.prefix);
279 h.update(msg);
280 var nonce64: [64]u8 = undefined;
281 h.final(&nonce64);
282
283 const nonce = Curve.scalar.reduce64(nonce64);
284 const r = try Curve.basePoint.mul(nonce);
285
286 var sig: [signature_length]u8 = undefined;
287 mem.copy(u8, sig[0..32], &r.toBytes());
288 mem.copy(u8, sig[32..], &key_pair.blind_public_key);
289 h = Sha512.init(.{});
290 h.update(&sig);
291 h.update(msg);
292 var hram64: [Sha512.digest_length]u8 = undefined;
293 h.final(&hram64);
294 const hram = Curve.scalar.reduce64(hram64);
295
296 const s = Curve.scalar.mulAdd(hram, key_pair.blind_secret_key.blind_scalar, nonce);
297 mem.copy(u8, sig[32..], s[0..]);
298 return sig;
299 }
300 };
210301};
211302
212303test "ed25519 key pair creation" {
......@@ -355,3 +446,26 @@ test "ed25519 test vectors" {
355446 }
356447 }
357448}
449
450test "ed25519 with blind keys" {
451 const BlindKeySignatures = Ed25519.BlindKeySignatures;
452
453 // Create a standard Ed25519 key pair
454 const kp = try Ed25519.KeyPair.create(null);
455
456 // Create a random blinding seed
457 var blind: [32]u8 = undefined;
458 crypto.random.bytes(&blind);
459
460 // Blind the key pair
461 const blind_kp = try BlindKeySignatures.blind(kp, blind);
462
463 // Sign a message and check that it can be verified with the blind public key
464 const msg = "test";
465 const sig = try BlindKeySignatures.sign(msg, blind_kp, null);
466 try Ed25519.verify(sig, msg, blind_kp.blind_public_key);
467
468 // Unblind the public key
469 const pk = try BlindKeySignatures.unblindPublicKey(blind_kp.blind_public_key, blind);
470 try std.testing.expectEqualSlices(u8, &pk, &kp.public_key);
471}