Type Function Ecdsa [src]
Elliptic Curve Digital Signature Algorithm (ECDSA).
Prototype
pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type
Parameters
Curve: type
Hash: type
Source
pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
const Prf = switch (Hash) {
sha3.Shake128 => sha3.KMac128,
sha3.Shake256 => sha3.KMac256,
else => crypto.auth.hmac.Hmac(Hash),
};
return struct {
/// Length (in bytes) of optional random bytes, for non-deterministic signatures.
pub const noise_length = Curve.scalar.encoded_length;
/// An ECDSA secret key.
pub const SecretKey = struct {
/// Length (in bytes) of a raw secret key.
pub const encoded_length = Curve.scalar.encoded_length;
bytes: Curve.scalar.CompressedScalar,
pub fn fromBytes(bytes: [encoded_length]u8) !SecretKey {
return SecretKey{ .bytes = bytes };
}
pub fn toBytes(sk: SecretKey) [encoded_length]u8 {
return sk.bytes;
}
};
/// An ECDSA public key.
pub const PublicKey = struct {
/// Length (in bytes) of a compressed sec1-encoded key.
pub const compressed_sec1_encoded_length = 1 + Curve.Fe.encoded_length;
/// Length (in bytes) of a compressed sec1-encoded key.
pub const uncompressed_sec1_encoded_length = 1 + 2 * Curve.Fe.encoded_length;
p: Curve,
/// Create a public key from a SEC-1 representation.
pub fn fromSec1(sec1: []const u8) !PublicKey {
return PublicKey{ .p = try Curve.fromSec1(sec1) };
}
/// Encode the public key using the compressed SEC-1 format.
pub fn toCompressedSec1(pk: PublicKey) [compressed_sec1_encoded_length]u8 {
return pk.p.toCompressedSec1();
}
/// Encoding the public key using the uncompressed SEC-1 format.
pub fn toUncompressedSec1(pk: PublicKey) [uncompressed_sec1_encoded_length]u8 {
return pk.p.toUncompressedSec1();
}
};
/// An ECDSA signature.
pub const Signature = struct {
/// Length (in bytes) of a raw signature.
pub const encoded_length = Curve.scalar.encoded_length * 2;
/// Maximum length (in bytes) of a DER-encoded signature.
pub const der_encoded_length_max = encoded_length + 2 + 2 * 3;
/// The R component of an ECDSA signature.
r: Curve.scalar.CompressedScalar,
/// The S component of an ECDSA signature.
s: Curve.scalar.CompressedScalar,
/// Create a Verifier for incremental verification of a signature.
pub fn verifier(sig: Signature, public_key: PublicKey) Verifier.InitError!Verifier {
return Verifier.init(sig, public_key);
}
pub const VerifyError = Verifier.InitError || Verifier.VerifyError;
/// Verify the signature against a message and public key.
/// Return IdentityElement or NonCanonical if the public key or signature are not in the expected range,
/// or SignatureVerificationError if the signature is invalid for the given message and key.
pub fn verify(sig: Signature, msg: []const u8, public_key: PublicKey) VerifyError!void {
var st = try sig.verifier(public_key);
st.update(msg);
try st.verify();
}
/// Return the raw signature (r, s) in big-endian format.
pub fn toBytes(sig: Signature) [encoded_length]u8 {
var bytes: [encoded_length]u8 = undefined;
@memcpy(bytes[0 .. encoded_length / 2], &sig.r);
@memcpy(bytes[encoded_length / 2 ..], &sig.s);
return bytes;
}
/// Create a signature from a raw encoding of (r, s).
/// ECDSA always assumes big-endian.
pub fn fromBytes(bytes: [encoded_length]u8) Signature {
return Signature{
.r = bytes[0 .. encoded_length / 2].*,
.s = bytes[encoded_length / 2 ..].*,
};
}
/// Encode the signature using the DER format.
/// The maximum length of the DER encoding is der_encoded_length_max.
/// The function returns a slice, that can be shorter than der_encoded_length_max.
pub fn toDer(sig: Signature, buf: *[der_encoded_length_max]u8) []u8 {
var fb = io.fixedBufferStream(buf);
const w = fb.writer();
const r_len = @as(u8, @intCast(sig.r.len + (sig.r[0] >> 7)));
const s_len = @as(u8, @intCast(sig.s.len + (sig.s[0] >> 7)));
const seq_len = @as(u8, @intCast(2 + r_len + 2 + s_len));
w.writeAll(&[_]u8{ 0x30, seq_len }) catch unreachable;
w.writeAll(&[_]u8{ 0x02, r_len }) catch unreachable;
if (sig.r[0] >> 7 != 0) {
w.writeByte(0x00) catch unreachable;
}
w.writeAll(&sig.r) catch unreachable;
w.writeAll(&[_]u8{ 0x02, s_len }) catch unreachable;
if (sig.s[0] >> 7 != 0) {
w.writeByte(0x00) catch unreachable;
}
w.writeAll(&sig.s) catch unreachable;
return fb.getWritten();
}
// Read a DER-encoded integer.
fn readDerInt(out: []u8, reader: anytype) EncodingError!void {
var buf: [2]u8 = undefined;
_ = reader.readNoEof(&buf) catch return error.InvalidEncoding;
if (buf[0] != 0x02) return error.InvalidEncoding;
var expected_len = @as(usize, buf[1]);
if (expected_len == 0 or expected_len > 1 + out.len) return error.InvalidEncoding;
var has_top_bit = false;
if (expected_len == 1 + out.len) {
if ((reader.readByte() catch return error.InvalidEncoding) != 0) return error.InvalidEncoding;
expected_len -= 1;
has_top_bit = true;
}
const out_slice = out[out.len - expected_len ..];
reader.readNoEof(out_slice) catch return error.InvalidEncoding;
if (has_top_bit and out[0] >> 7 == 0) return error.InvalidEncoding;
}
/// Create a signature from a DER representation.
/// Returns InvalidEncoding if the DER encoding is invalid.
pub fn fromDer(der: []const u8) EncodingError!Signature {
var sig: Signature = mem.zeroInit(Signature, .{});
var fb = io.fixedBufferStream(der);
const reader = fb.reader();
var buf: [2]u8 = undefined;
_ = reader.readNoEof(&buf) catch return error.InvalidEncoding;
if (buf[0] != 0x30 or @as(usize, buf[1]) + 2 != der.len) {
return error.InvalidEncoding;
}
try readDerInt(&sig.r, reader);
try readDerInt(&sig.s, reader);
if (fb.getPos() catch unreachable != der.len) return error.InvalidEncoding;
return sig;
}
};
/// A Signer is used to incrementally compute a signature.
/// It can be obtained from a `KeyPair`, using the `signer()` function.
pub const Signer = struct {
h: Hash,
secret_key: SecretKey,
noise: ?[noise_length]u8,
fn init(secret_key: SecretKey, noise: ?[noise_length]u8) !Signer {
return Signer{
.h = Hash.init(.{}),
.secret_key = secret_key,
.noise = noise,
};
}
/// Add new data to the message being signed.
pub fn update(self: *Signer, data: []const u8) void {
self.h.update(data);
}
/// Compute a signature over the entire message.
pub fn finalize(self: *Signer) (IdentityElementError || NonCanonicalError)!Signature {
const scalar_encoded_length = Curve.scalar.encoded_length;
const h_len = @max(Hash.digest_length, scalar_encoded_length);
var h: [h_len]u8 = [_]u8{0} ** h_len;
const h_slice = h[h_len - Hash.digest_length .. h_len];
self.h.final(h_slice);
std.debug.assert(h.len >= scalar_encoded_length);
const z = reduceToScalar(scalar_encoded_length, h[0..scalar_encoded_length].*);
const k = deterministicScalar(h_slice.*, self.secret_key.bytes, self.noise);
const p = try Curve.basePoint.mul(k.toBytes(.big), .big);
const xs = p.affineCoordinates().x.toBytes(.big);
const r = reduceToScalar(Curve.Fe.encoded_length, xs);
if (r.isZero()) return error.IdentityElement;
const k_inv = k.invert();
const zrs = z.add(r.mul(try Curve.scalar.Scalar.fromBytes(self.secret_key.bytes, .big)));
const s = k_inv.mul(zrs);
if (s.isZero()) return error.IdentityElement;
return Signature{ .r = r.toBytes(.big), .s = s.toBytes(.big) };
}
};
/// A Verifier is used to incrementally verify a signature.
/// It can be obtained from a `Signature`, using the `verifier()` function.
pub const Verifier = struct {
h: Hash,
r: Curve.scalar.Scalar,
s: Curve.scalar.Scalar,
public_key: PublicKey,
pub const InitError = IdentityElementError || NonCanonicalError;
fn init(sig: Signature, public_key: PublicKey) InitError!Verifier {
const r = try Curve.scalar.Scalar.fromBytes(sig.r, .big);
const s = try Curve.scalar.Scalar.fromBytes(sig.s, .big);
if (r.isZero() or s.isZero()) return error.IdentityElement;
return Verifier{
.h = Hash.init(.{}),
.r = r,
.s = s,
.public_key = public_key,
};
}
/// Add new content to the message to be verified.
pub fn update(self: *Verifier, data: []const u8) void {
self.h.update(data);
}
pub const VerifyError = IdentityElementError || NonCanonicalError ||
SignatureVerificationError;
/// Verify that the signature is valid for the entire message.
pub fn verify(self: *Verifier) VerifyError!void {
const ht = Curve.scalar.encoded_length;
const h_len = @max(Hash.digest_length, ht);
var h: [h_len]u8 = [_]u8{0} ** h_len;
self.h.final(h[h_len - Hash.digest_length .. h_len]);
const z = reduceToScalar(ht, h[0..ht].*);
if (z.isZero()) {
return error.SignatureVerificationFailed;
}
const s_inv = self.s.invert();
const v1 = z.mul(s_inv).toBytes(.little);
const v2 = self.r.mul(s_inv).toBytes(.little);
const v1g = try Curve.basePoint.mulPublic(v1, .little);
const v2pk = try self.public_key.p.mulPublic(v2, .little);
const vxs = v1g.add(v2pk).affineCoordinates().x.toBytes(.big);
const vr = reduceToScalar(Curve.Fe.encoded_length, vxs);
if (!self.r.equivalent(vr)) {
return error.SignatureVerificationFailed;
}
}
};
/// An ECDSA key pair.
pub const KeyPair = struct {
/// Length (in bytes) of a seed required to create a key pair.
pub const seed_length = noise_length;
/// Public part.
public_key: PublicKey,
/// Secret scalar.
secret_key: SecretKey,
/// Deterministically derive a key pair from a cryptograpically secure secret seed.
///
/// Except in tests, applications should generally call `generate()` instead of this function.
pub fn generateDeterministic(seed: [seed_length]u8) IdentityElementError!KeyPair {
const h = [_]u8{0x00} ** Hash.digest_length;
const k0 = [_]u8{0x01} ** SecretKey.encoded_length;
const secret_key = deterministicScalar(h, k0, seed).toBytes(.big);
return fromSecretKey(SecretKey{ .bytes = secret_key });
}
/// Generate a new, random key pair.
pub fn generate() KeyPair {
var random_seed: [seed_length]u8 = undefined;
while (true) {
crypto.random.bytes(&random_seed);
return generateDeterministic(random_seed) catch {
@branchHint(.unlikely);
continue;
};
}
}
/// Return the public key corresponding to the secret key.
pub fn fromSecretKey(secret_key: SecretKey) IdentityElementError!KeyPair {
const public_key = try Curve.basePoint.mul(secret_key.bytes, .big);
return KeyPair{ .secret_key = secret_key, .public_key = PublicKey{ .p = public_key } };
}
/// Sign a message using the key pair.
/// The noise can be null in order to create deterministic signatures.
/// If deterministic signatures are not required, the noise should be randomly generated instead.
/// This helps defend against fault attacks.
pub fn sign(key_pair: KeyPair, msg: []const u8, noise: ?[noise_length]u8) (IdentityElementError || NonCanonicalError)!Signature {
var st = try key_pair.signer(noise);
st.update(msg);
return st.finalize();
}
/// Create a Signer, that can be used for incremental signature verification.
pub fn signer(key_pair: KeyPair, noise: ?[noise_length]u8) !Signer {
return Signer.init(key_pair.secret_key, noise);
}
};
// Reduce the coordinate of a field element to the scalar field.
fn reduceToScalar(comptime unreduced_len: usize, s: [unreduced_len]u8) Curve.scalar.Scalar {
if (unreduced_len >= 48) {
var xs = [_]u8{0} ** 64;
@memcpy(xs[xs.len - s.len ..], s[0..]);
return Curve.scalar.Scalar.fromBytes64(xs, .big);
}
var xs = [_]u8{0} ** 48;
@memcpy(xs[xs.len - s.len ..], s[0..]);
return Curve.scalar.Scalar.fromBytes48(xs, .big);
}
// Create a deterministic scalar according to a secret key and optional noise.
// This uses the overly conservative scheme from the "Deterministic ECDSA and EdDSA Signatures with Additional Randomness" draft.
fn deterministicScalar(h: [Hash.digest_length]u8, secret_key: Curve.scalar.CompressedScalar, noise: ?[noise_length]u8) Curve.scalar.Scalar {
var k = [_]u8{0x00} ** h.len;
var m = [_]u8{0x00} ** (h.len + 1 + noise_length + secret_key.len + h.len);
var t = [_]u8{0x00} ** Curve.scalar.encoded_length;
const m_v = m[0..h.len];
const m_i = &m[m_v.len];
const m_z = m[m_v.len + 1 ..][0..noise_length];
const m_x = m[m_v.len + 1 + noise_length ..][0..secret_key.len];
const m_h = m[m.len - h.len ..];
@memset(m_v, 0x01);
m_i.* = 0x00;
if (noise) |n| @memcpy(m_z, &n);
@memcpy(m_x, &secret_key);
@memcpy(m_h, &h);
Prf.create(&k, &m, &k);
Prf.create(m_v, m_v, &k);
m_i.* = 0x01;
Prf.create(&k, &m, &k);
Prf.create(m_v, m_v, &k);
while (true) {
var t_off: usize = 0;
while (t_off < t.len) : (t_off += m_v.len) {
const t_end = @min(t_off + m_v.len, t.len);
Prf.create(m_v, m_v, &k);
@memcpy(t[t_off..t_end], m_v[0 .. t_end - t_off]);
}
if (Curve.scalar.Scalar.fromBytes(t, .big)) |s| return s else |_| {}
m_i.* = 0x00;
Prf.create(&k, m[0 .. m_v.len + 1], &k);
Prf.create(m_v, m_v, &k);
}
}
};
}