├── .gitignore ├── LICENSE ├── README.md ├── build.zig └── src └── main.zig /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | zig-cache 3 | zig-out 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2023 Frank Denis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EdDSA signatures with blind keys 2 | 3 | A Zig implementation of the [EdDSA key blinding](https://chris-wood.github.io/draft-wood-cfrg-eddsa-blinding/draft-wood-cfrg-eddsa-blinding.html) proposal. 4 | 5 | ```zig 6 | // Create a standard Ed25519 key pair 7 | const kp = try Ed25519.KeyPair.create(null); 8 | 9 | // Create a random blinding seed 10 | var blind: [32]u8 = undefined; 11 | crypto.random.bytes(&blind); 12 | 13 | // Blind the key pair 14 | const blind_kp = try BlindEd25519.blind(kp, blind); 15 | 16 | // Sign a message and check that it can be verified with the blind public key 17 | const msg = "test"; 18 | const sig = try BlindEd25519.sign(msg, blind_kp, null); 19 | try Ed25519.verify(sig, msg, blind_kp.blind_public_key); 20 | 21 | // Unblind the public key 22 | const pk = try BlindEd25519.unblind_public_key(blind_kp.blind_public_key, blind); 23 | try std.testing.expectEqualSlices(u8, &pk, &kp.public_key); 24 | ``` 25 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.build.Builder) void { 4 | // Standard release options allow the person running `zig build` to select 5 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. 6 | const mode = b.standardReleaseOptions(); 7 | 8 | const lib = b.addStaticLibrary("zig-eddsa-key-blinding", "src/main.zig"); 9 | lib.setBuildMode(mode); 10 | lib.install(); 11 | 12 | const main_tests = b.addTest("src/main.zig"); 13 | main_tests.setBuildMode(mode); 14 | 15 | const test_step = b.step("test", "Run library tests"); 16 | test_step.dependOn(&main_tests.step); 17 | } 18 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const crypto = std.crypto; 3 | const mem = std.mem; 4 | const Sha512 = crypto.hash.sha2.Sha512; 5 | const Curve = crypto.ecc.Edwards25519; 6 | const Scalar = Curve.scalar.Scalar; 7 | const Ed25519 = crypto.sign.Ed25519; 8 | const CompressedScalar = Curve.scalar.CompressedScalar; 9 | 10 | /// Ed25519 signatures with blind keys. 11 | pub const BlindEd25519 = struct { 12 | /// Length (in bytes) of optional random bytes, for non-deterministic signatures. 13 | pub const noise_length = Ed25519.noise_length; 14 | /// Length (in bytes) of a signature. 15 | pub const signature_length = Ed25519.signature_length; 16 | /// Length (in bytes) of a compressed public key. 17 | pub const public_key_length = Ed25519.public_length; 18 | /// Length (in bytes) of a blinding seed. 19 | pub const blind_seed_length = 32; 20 | 21 | /// A blind secret key. 22 | pub const BlindSecretKey = struct { 23 | prefix: [64]u8, 24 | blind_scalar: CompressedScalar, 25 | blind_public_key: CompressedScalar, 26 | }; 27 | 28 | /// A blind key pair. 29 | pub const BlindKeyPair = struct { 30 | blind_public_key: [public_key_length]u8, 31 | blind_secret_key: BlindSecretKey, 32 | }; 33 | 34 | /// Blind an existing key pair with a blinding seed. 35 | pub fn blind(key_pair: Ed25519.KeyPair, blind_seed: [blind_seed_length]u8, ctx: []const u8) !BlindKeyPair { 36 | var h: [Sha512.digest_length]u8 = undefined; 37 | Sha512.hash(key_pair.secret_key[0..32], &h, .{}); 38 | Curve.scalar.clamp(h[0..32]); 39 | const scalar = Curve.scalar.reduce(h[0..32].*); 40 | 41 | var blind_h: [Sha512.digest_length]u8 = undefined; 42 | var hx = Sha512.init(.{}); 43 | hx.update(blind_seed[0..]); 44 | hx.update(&[1]u8{0}); 45 | hx.update(ctx); 46 | hx.final(&blind_h); 47 | const blind_factor = Curve.scalar.reduce(blind_h[0..32].*); 48 | 49 | const blind_scalar = Curve.scalar.mul(scalar, blind_factor); 50 | const blind_public_key = (Curve.basePoint.mul(blind_scalar) catch return error.IdentityElement).toBytes(); 51 | 52 | var prefix: [64]u8 = undefined; 53 | mem.copy(u8, prefix[0..32], h[32..64]); 54 | mem.copy(u8, prefix[32..64], blind_h[32..64]); 55 | 56 | const blind_secret_key = .{ 57 | .prefix = prefix, 58 | .blind_scalar = blind_scalar, 59 | .blind_public_key = blind_public_key, 60 | }; 61 | return BlindKeyPair{ 62 | .blind_public_key = blind_public_key, 63 | .blind_secret_key = blind_secret_key, 64 | }; 65 | } 66 | 67 | /// Recover a public key from a blind version of it. 68 | pub fn unblindPublicKey(blind_public_key: [public_key_length]u8, blind_seed: [blind_seed_length]u8, ctx: []const u8) ![public_key_length]u8 { 69 | var blind_h: [Sha512.digest_length]u8 = undefined; 70 | var hx = Sha512.init(.{}); 71 | hx.update(blind_seed[0..]); 72 | hx.update(&[1]u8{0}); 73 | hx.update(ctx); 74 | hx.final(&blind_h); 75 | const inv_blind_factor = Scalar.fromBytes(blind_h[0..32].*).invert().toBytes(); 76 | const public_key = try (try Curve.fromBytes(blind_public_key)).mul(inv_blind_factor); 77 | return public_key.toBytes(); 78 | } 79 | 80 | /// Sign a message using a blind key pair, and optional random noise. 81 | /// Having noise creates non-standard, non-deterministic signatures, 82 | /// but has been proven to increase resilience against fault attacks. 83 | pub fn sign(msg: []const u8, key_pair: BlindKeyPair, noise: ?[noise_length]u8) ![signature_length]u8 { 84 | var h = Sha512.init(.{}); 85 | if (noise) |*z| { 86 | h.update(z); 87 | } 88 | h.update(&key_pair.blind_secret_key.prefix); 89 | h.update(msg); 90 | var nonce64: [64]u8 = undefined; 91 | h.final(&nonce64); 92 | 93 | const nonce = Curve.scalar.reduce64(nonce64); 94 | const r = try Curve.basePoint.mul(nonce); 95 | 96 | var sig: [signature_length]u8 = undefined; 97 | mem.copy(u8, sig[0..32], &r.toBytes()); 98 | mem.copy(u8, sig[32..], &key_pair.blind_public_key); 99 | h = Sha512.init(.{}); 100 | h.update(&sig); 101 | h.update(msg); 102 | var hram64: [Sha512.digest_length]u8 = undefined; 103 | h.final(&hram64); 104 | const hram = Curve.scalar.reduce64(hram64); 105 | 106 | const s = Curve.scalar.mulAdd(hram, key_pair.blind_secret_key.blind_scalar, nonce); 107 | mem.copy(u8, sig[32..], s[0..]); 108 | return sig; 109 | } 110 | }; 111 | 112 | test "Blind key EdDSA signature" { 113 | // Create a standard Ed25519 key pair 114 | const kp = try Ed25519.KeyPair.create(null); 115 | 116 | // Create a random blinding seed 117 | var blind: [32]u8 = undefined; 118 | crypto.random.bytes(&blind); 119 | 120 | // Blind the key pair 121 | const blind_kp = try BlindEd25519.blind(kp, blind, "ctx"); 122 | 123 | // Sign a message and check that it can be verified with the blind public key 124 | const msg = "test"; 125 | const sig = try BlindEd25519.sign(msg, blind_kp, null); 126 | try Ed25519.verify(sig, msg, blind_kp.blind_public_key); 127 | 128 | // Unblind the public key 129 | const pk = try BlindEd25519.unblindPublicKey(blind_kp.blind_public_key, blind, "ctx"); 130 | try std.testing.expectEqualSlices(u8, &pk, &kp.public_key); 131 | } 132 | --------------------------------------------------------------------------------