| ... | @@ -28,6 +28,9 @@ pub const Ed25519 = struct { | ... | @@ -28,6 +28,9 @@ pub const Ed25519 = struct { |
| 28 | /// Length (in bytes) of optional random bytes, for non-deterministic signatures. | 28 | /// Length (in bytes) of optional random bytes, for non-deterministic signatures. |
| 29 | pub const noise_length = 32; | 29 | pub const noise_length = 32; |
| 30 | | 30 | |
| | 31 | const CompressedScalar = Curve.scalar.CompressedScalar; |
| | 32 | const Scalar = Curve.scalar.Scalar; |
| | 33 | |
| 31 | /// An Ed25519 key pair. | 34 | /// An Ed25519 key pair. |
| 32 | pub const KeyPair = struct { | 35 | pub const KeyPair = struct { |
| 33 | /// Public part. | 36 | /// Public part. |
| ... | @@ -207,6 +210,94 @@ pub const Ed25519 = struct { | ... | @@ -207,6 +210,94 @@ pub const Ed25519 = struct { |
| 207 | return error.SignatureVerificationFailed; | 210 | return error.SignatureVerificationFailed; |
| 208 | } else |_| {} | 211 | } else |_| {} |
| 209 | } | 212 | } |
| | 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 | }; |
| 210 | }; | 301 | }; |
| 211 | | 302 | |
| 212 | test "ed25519 key pair creation" { | 303 | test "ed25519 key pair creation" { |
| ... | @@ -355,3 +446,26 @@ test "ed25519 test vectors" { | ... | @@ -355,3 +446,26 @@ test "ed25519 test vectors" { |
| 355 | } | 446 | } |
| 356 | } | 447 | } |
| 357 | } | 448 | } |
| | 449 | |
| | 450 | test "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 | } |