mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
std.crypto.ecc: add support for the secp256k1 curve (#11880)
std.crypto.ecc: add support for the secp256k1 curve Usage of the secp256k1 elliptic curve recently grew exponentially, since this is the curve used by Bitcoin and other popular blockchains such as Ethereum. With this, Zig has support for all the widely deployed elliptic curves today.
This commit is contained in:
parent
ea13437ac5
commit
234ccb4a50
@ -64,6 +64,7 @@ pub const ecc = struct {
|
||||
pub const P256 = @import("crypto/pcurves/p256.zig").P256;
|
||||
pub const P384 = @import("crypto/pcurves/p384.zig").P384;
|
||||
pub const Ristretto255 = @import("crypto/25519/ristretto255.zig").Ristretto255;
|
||||
pub const Secp256k1 = @import("crypto/pcurves/secp256k1.zig").Secp256k1;
|
||||
};
|
||||
|
||||
/// Hash functions.
|
||||
@ -205,6 +206,7 @@ test {
|
||||
_ = ecc.P256;
|
||||
_ = ecc.P384;
|
||||
_ = ecc.Ristretto255;
|
||||
_ = ecc.Secp256k1;
|
||||
|
||||
_ = hash.blake2;
|
||||
_ = hash.Blake3;
|
||||
|
||||
@ -295,6 +295,17 @@ pub fn Field(comptime params: FieldParams) type {
|
||||
const x63 = x32.sqn(31).mul(x31);
|
||||
const x126 = x63.sqn(63).mul(x63);
|
||||
return x126.sqn(126).mul(x126).sqn(3).mul(t111).sqn(33).mul(x32).sqn(64).mul(x2).sqn(30);
|
||||
} else if (field_order == 115792089237316195423570985008687907853269984665640564039457584007908834671663) {
|
||||
const t11 = x2.mul(x2.sq());
|
||||
const t1111 = t11.mul(t11.sqn(2));
|
||||
const t11111 = x2.mul(t1111.sq());
|
||||
const t1111111 = t11.mul(t11111.sqn(2));
|
||||
const x11 = t1111111.sqn(4).mul(t1111);
|
||||
const x22 = x11.sqn(11).mul(x11);
|
||||
const x27 = x22.sqn(5).mul(t11111);
|
||||
const x54 = x27.sqn(27).mul(x27);
|
||||
const x108 = x54.sqn(54).mul(x54);
|
||||
return x108.sqn(108).mul(x108).sqn(7).mul(t1111111).sqn(23).mul(x22).sqn(6).mul(t11).sqn(2);
|
||||
} else {
|
||||
return x2.pow(std.meta.Int(.unsigned, field_bits), (field_order + 1) / 4);
|
||||
}
|
||||
|
||||
556
lib/std/crypto/pcurves/secp256k1.zig
Normal file
556
lib/std/crypto/pcurves/secp256k1.zig
Normal file
@ -0,0 +1,556 @@
|
||||
const std = @import("std");
|
||||
const crypto = std.crypto;
|
||||
const math = std.math;
|
||||
const mem = std.mem;
|
||||
const meta = std.meta;
|
||||
|
||||
const EncodingError = crypto.errors.EncodingError;
|
||||
const IdentityElementError = crypto.errors.IdentityElementError;
|
||||
const NonCanonicalError = crypto.errors.NonCanonicalError;
|
||||
const NotSquareError = crypto.errors.NotSquareError;
|
||||
|
||||
/// Group operations over secp256k1.
|
||||
pub const Secp256k1 = struct {
|
||||
/// The underlying prime field.
|
||||
pub const Fe = @import("secp256k1/field.zig").Fe;
|
||||
/// Field arithmetic mod the order of the main subgroup.
|
||||
pub const scalar = @import("secp256k1/scalar.zig");
|
||||
|
||||
x: Fe,
|
||||
y: Fe,
|
||||
z: Fe = Fe.one,
|
||||
|
||||
is_base: bool = false,
|
||||
|
||||
/// The secp256k1 base point.
|
||||
pub const basePoint = Secp256k1{
|
||||
.x = Fe.fromInt(55066263022277343669578718895168534326250603453777594175500187360389116729240) catch unreachable,
|
||||
.y = Fe.fromInt(32670510020758816978083085130507043184471273380659243275938904335757337482424) catch unreachable,
|
||||
.z = Fe.one,
|
||||
.is_base = true,
|
||||
};
|
||||
|
||||
/// The secp256k1 neutral element.
|
||||
pub const identityElement = Secp256k1{ .x = Fe.zero, .y = Fe.one, .z = Fe.zero };
|
||||
|
||||
pub const B = Fe.fromInt(7) catch unreachable;
|
||||
|
||||
pub const Endormorphism = struct {
|
||||
const lambda: u256 = 37718080363155996902926221483475020450927657555482586988616620542887997980018;
|
||||
const beta: u256 = 55594575648329892869085402983802832744385952214688224221778511981742606582254;
|
||||
|
||||
const lambda_s = s: {
|
||||
var buf: [32]u8 = undefined;
|
||||
mem.writeIntLittle(u256, &buf, Endormorphism.lambda);
|
||||
break :s buf;
|
||||
};
|
||||
|
||||
pub const SplitScalar = struct {
|
||||
r1: [32]u8,
|
||||
r2: [32]u8,
|
||||
};
|
||||
|
||||
/// Compute r1 and r2 so that k = r1 + r2*lambda (mod L).
|
||||
pub fn splitScalar(s: [32]u8, endian: std.builtin.Endian) SplitScalar {
|
||||
const b1_neg_s = comptime s: {
|
||||
var buf: [32]u8 = undefined;
|
||||
mem.writeIntLittle(u256, &buf, 303414439467246543595250775667605759171);
|
||||
break :s buf;
|
||||
};
|
||||
const b2_neg_s = comptime s: {
|
||||
var buf: [32]u8 = undefined;
|
||||
mem.writeIntLittle(u256, &buf, scalar.field_order - 64502973549206556628585045361533709077);
|
||||
break :s buf;
|
||||
};
|
||||
const k = mem.readInt(u256, &s, endian);
|
||||
|
||||
const t1 = math.mulWide(u256, k, 21949224512762693861512883645436906316123769664773102907882521278123970637873);
|
||||
const t2 = math.mulWide(u256, k, 103246583619904461035481197785446227098457807945486720222659797044629401272177);
|
||||
|
||||
const c1 = @truncate(u128, t1 >> 384) + @truncate(u1, t1 >> 383);
|
||||
const c2 = @truncate(u128, t2 >> 384) + @truncate(u1, t2 >> 383);
|
||||
|
||||
var buf: [32]u8 = undefined;
|
||||
|
||||
mem.writeIntLittle(u256, &buf, c1);
|
||||
const c1x = scalar.mul(buf, b1_neg_s, .Little) catch unreachable;
|
||||
|
||||
mem.writeIntLittle(u256, &buf, c2);
|
||||
const c2x = scalar.mul(buf, b2_neg_s, .Little) catch unreachable;
|
||||
|
||||
const r2 = scalar.add(c1x, c2x, .Little) catch unreachable;
|
||||
|
||||
var r1 = scalar.mul(r2, lambda_s, .Little) catch unreachable;
|
||||
r1 = scalar.sub(s, r1, .Little) catch unreachable;
|
||||
|
||||
return SplitScalar{ .r1 = r1, .r2 = r2 };
|
||||
}
|
||||
};
|
||||
|
||||
/// Reject the neutral element.
|
||||
pub fn rejectIdentity(p: Secp256k1) IdentityElementError!void {
|
||||
if (p.x.isZero()) {
|
||||
return error.IdentityElement;
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a point from affine coordinates after checking that they match the curve equation.
|
||||
pub fn fromAffineCoordinates(p: AffineCoordinates) EncodingError!Secp256k1 {
|
||||
const x = p.x;
|
||||
const y = p.y;
|
||||
const x3B = x.sq().mul(x).add(B);
|
||||
const yy = y.sq();
|
||||
const on_curve = @boolToInt(x3B.equivalent(yy));
|
||||
const is_identity = @boolToInt(x.equivalent(AffineCoordinates.identityElement.x)) & @boolToInt(y.equivalent(AffineCoordinates.identityElement.y));
|
||||
if ((on_curve | is_identity) == 0) {
|
||||
return error.InvalidEncoding;
|
||||
}
|
||||
var ret = Secp256k1{ .x = x, .y = y, .z = Fe.one };
|
||||
ret.z.cMov(Secp256k1.identityElement.z, is_identity);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Create a point from serialized affine coordinates.
|
||||
pub fn fromSerializedAffineCoordinates(xs: [32]u8, ys: [32]u8, endian: std.builtin.Endian) (NonCanonicalError || EncodingError)!Secp256k1 {
|
||||
const x = try Fe.fromBytes(xs, endian);
|
||||
const y = try Fe.fromBytes(ys, endian);
|
||||
return fromAffineCoordinates(.{ .x = x, .y = y });
|
||||
}
|
||||
|
||||
/// Recover the Y coordinate from the X coordinate.
|
||||
pub fn recoverY(x: Fe, is_odd: bool) NotSquareError!Fe {
|
||||
const x3B = x.sq().mul(x).add(B);
|
||||
var y = try x3B.sqrt();
|
||||
const yn = y.neg();
|
||||
y.cMov(yn, @boolToInt(is_odd) ^ @boolToInt(y.isOdd()));
|
||||
return y;
|
||||
}
|
||||
|
||||
/// Deserialize a SEC1-encoded point.
|
||||
pub fn fromSec1(s: []const u8) (EncodingError || NotSquareError || NonCanonicalError)!Secp256k1 {
|
||||
if (s.len < 1) return error.InvalidEncoding;
|
||||
const encoding_type = s[0];
|
||||
const encoded = s[1..];
|
||||
switch (encoding_type) {
|
||||
0 => {
|
||||
if (encoded.len != 0) return error.InvalidEncoding;
|
||||
return Secp256k1.identityElement;
|
||||
},
|
||||
2, 3 => {
|
||||
if (encoded.len != 32) return error.InvalidEncoding;
|
||||
const x = try Fe.fromBytes(encoded[0..32].*, .Big);
|
||||
const y_is_odd = (encoding_type == 3);
|
||||
const y = try recoverY(x, y_is_odd);
|
||||
return Secp256k1{ .x = x, .y = y };
|
||||
},
|
||||
4 => {
|
||||
if (encoded.len != 64) return error.InvalidEncoding;
|
||||
const x = try Fe.fromBytes(encoded[0..32].*, .Big);
|
||||
const y = try Fe.fromBytes(encoded[32..64].*, .Big);
|
||||
return Secp256k1.fromAffineCoordinates(.{ .x = x, .y = y });
|
||||
},
|
||||
else => return error.InvalidEncoding,
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize a point using the compressed SEC-1 format.
|
||||
pub fn toCompressedSec1(p: Secp256k1) [33]u8 {
|
||||
var out: [33]u8 = undefined;
|
||||
const xy = p.affineCoordinates();
|
||||
out[0] = if (xy.y.isOdd()) 3 else 2;
|
||||
mem.copy(u8, out[1..], &xy.x.toBytes(.Big));
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Serialize a point using the uncompressed SEC-1 format.
|
||||
pub fn toUncompressedSec1(p: Secp256k1) [65]u8 {
|
||||
var out: [65]u8 = undefined;
|
||||
out[0] = 4;
|
||||
const xy = p.affineCoordinates();
|
||||
mem.copy(u8, out[1..33], &xy.x.toBytes(.Big));
|
||||
mem.copy(u8, out[33..65], &xy.y.toBytes(.Big));
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Return a random point.
|
||||
pub fn random() Secp256k1 {
|
||||
const n = scalar.random(.Little);
|
||||
return basePoint.mul(n, .Little) catch unreachable;
|
||||
}
|
||||
|
||||
/// Flip the sign of the X coordinate.
|
||||
pub fn neg(p: Secp256k1) Secp256k1 {
|
||||
return .{ .x = p.x, .y = p.y.neg(), .z = p.z };
|
||||
}
|
||||
|
||||
/// Double a secp256k1 point.
|
||||
// Algorithm 9 from https://eprint.iacr.org/2015/1060.pdf
|
||||
pub fn dbl(p: Secp256k1) Secp256k1 {
|
||||
var t0 = p.y.sq();
|
||||
var Z3 = t0.dbl();
|
||||
Z3 = Z3.dbl();
|
||||
Z3 = Z3.dbl();
|
||||
var t1 = p.y.mul(p.z);
|
||||
var t2 = p.z.sq();
|
||||
// b3 = (2^2)^2 + 2^2 + 1
|
||||
const t2_4 = t2.dbl().dbl();
|
||||
t2 = t2_4.dbl().dbl().add(t2_4).add(t2);
|
||||
var X3 = t2.mul(Z3);
|
||||
var Y3 = t0.add(t2);
|
||||
Z3 = t1.mul(Z3);
|
||||
t1 = t2.dbl();
|
||||
t2 = t1.add(t2);
|
||||
t0 = t0.sub(t2);
|
||||
Y3 = t0.mul(Y3);
|
||||
Y3 = X3.add(Y3);
|
||||
t1 = p.x.mul(p.y);
|
||||
X3 = t0.mul(t1);
|
||||
X3 = X3.dbl();
|
||||
return .{
|
||||
.x = X3,
|
||||
.y = Y3,
|
||||
.z = Z3,
|
||||
};
|
||||
}
|
||||
|
||||
/// Add secp256k1 points, the second being specified using affine coordinates.
|
||||
// Algorithm 8 from https://eprint.iacr.org/2015/1060.pdf
|
||||
pub fn addMixed(p: Secp256k1, q: AffineCoordinates) Secp256k1 {
|
||||
var t0 = p.x.mul(q.x);
|
||||
var t1 = p.y.mul(q.y);
|
||||
var t3 = q.x.add(q.y);
|
||||
var t4 = p.x.add(p.y1);
|
||||
t3 = t3.mul(t4);
|
||||
t4 = t0.add(t1);
|
||||
t3 = t3.sub(t4);
|
||||
t4 = q.y.mul(p.z);
|
||||
t4 = t4.add(p.y);
|
||||
var Y3 = q.x.mul(p.z);
|
||||
Y3 = Y3.add(p.x);
|
||||
var X3 = t0.dbl();
|
||||
t0 = X3.add(t0);
|
||||
// b3 = (2^2)^2 + 2^2 + 1
|
||||
const t2_4 = p.z.dbl().dbl();
|
||||
var t2 = t2_4.dbl().dbl().add(t2_4).add(p.z);
|
||||
var Z3 = t1.add(t2);
|
||||
t1 = t1.sub(t2);
|
||||
const Y3_4 = Y3.dbl().dbl();
|
||||
Y3 = Y3_4.dbl().dbl().add(Y3_4).add(Y3);
|
||||
X3 = t4.mul(Y3);
|
||||
t2 = t3.mul(t1);
|
||||
X3 = t2.sub(X3);
|
||||
Y3 = Y3.mul(t0);
|
||||
t1 = t1.mul(Z3);
|
||||
Y3 = t1.add(Y3);
|
||||
t0 = t0.mul(t3);
|
||||
Z3 = Z3.mul(t4);
|
||||
Z3 = Z3.add(t0);
|
||||
|
||||
var ret = Secp256k1{
|
||||
.x = X3,
|
||||
.y = Y3,
|
||||
.z = Z3,
|
||||
};
|
||||
ret.cMov(p, @boolToInt(q.x.isZero()));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Add secp256k1 points.
|
||||
// Algorithm 7 from https://eprint.iacr.org/2015/1060.pdf
|
||||
pub fn add(p: Secp256k1, q: Secp256k1) Secp256k1 {
|
||||
var t0 = p.x.mul(q.x);
|
||||
var t1 = p.y.mul(q.y);
|
||||
var t2 = p.z.mul(q.z);
|
||||
var t3 = p.x.add(p.y);
|
||||
var t4 = q.x.add(q.y);
|
||||
t3 = t3.mul(t4);
|
||||
t4 = t0.add(t1);
|
||||
t3 = t3.sub(t4);
|
||||
t4 = p.y.add(p.z);
|
||||
var X3 = q.y.add(q.z);
|
||||
t4 = t4.mul(X3);
|
||||
X3 = t1.add(t2);
|
||||
t4 = t4.sub(X3);
|
||||
X3 = p.x.add(p.z);
|
||||
var Y3 = q.x.add(q.z);
|
||||
X3 = X3.mul(Y3);
|
||||
Y3 = t0.add(t2);
|
||||
Y3 = X3.sub(Y3);
|
||||
X3 = t0.dbl();
|
||||
t0 = X3.add(t0);
|
||||
// b3 = (2^2)^2 + 2^2 + 1
|
||||
const t2_4 = t2.dbl().dbl();
|
||||
t2 = t2_4.dbl().dbl().add(t2_4).add(t2);
|
||||
var Z3 = t1.add(t2);
|
||||
t1 = t1.sub(t2);
|
||||
const Y3_4 = Y3.dbl().dbl();
|
||||
Y3 = Y3_4.dbl().dbl().add(Y3_4).add(Y3);
|
||||
X3 = t4.mul(Y3);
|
||||
t2 = t3.mul(t1);
|
||||
X3 = t2.sub(X3);
|
||||
Y3 = Y3.mul(t0);
|
||||
t1 = t1.mul(Z3);
|
||||
Y3 = t1.add(Y3);
|
||||
t0 = t0.mul(t3);
|
||||
Z3 = Z3.mul(t4);
|
||||
Z3 = Z3.add(t0);
|
||||
|
||||
return .{
|
||||
.x = X3,
|
||||
.y = Y3,
|
||||
.z = Z3,
|
||||
};
|
||||
}
|
||||
|
||||
/// Subtract secp256k1 points.
|
||||
pub fn sub(p: Secp256k1, q: Secp256k1) Secp256k1 {
|
||||
return p.add(q.neg());
|
||||
}
|
||||
|
||||
/// Subtract secp256k1 points, the second being specified using affine coordinates.
|
||||
pub fn subMixed(p: Secp256k1, q: AffineCoordinates) Secp256k1 {
|
||||
return p.addMixed(q.neg());
|
||||
}
|
||||
|
||||
/// Return affine coordinates.
|
||||
pub fn affineCoordinates(p: Secp256k1) AffineCoordinates {
|
||||
const zinv = p.z.invert();
|
||||
var ret = AffineCoordinates{
|
||||
.x = p.x.mul(zinv),
|
||||
.y = p.y.mul(zinv),
|
||||
};
|
||||
ret.cMov(AffineCoordinates.identityElement, @boolToInt(p.x.isZero()));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Return true if both coordinate sets represent the same point.
|
||||
pub fn equivalent(a: Secp256k1, b: Secp256k1) bool {
|
||||
if (a.sub(b).rejectIdentity()) {
|
||||
return false;
|
||||
} else |_| {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
fn cMov(p: *Secp256k1, a: Secp256k1, c: u1) void {
|
||||
p.x.cMov(a.x, c);
|
||||
p.y.cMov(a.y, c);
|
||||
p.z.cMov(a.z, c);
|
||||
}
|
||||
|
||||
fn pcSelect(comptime n: usize, pc: *const [n]Secp256k1, b: u8) Secp256k1 {
|
||||
var t = Secp256k1.identityElement;
|
||||
comptime var i: u8 = 1;
|
||||
inline while (i < pc.len) : (i += 1) {
|
||||
t.cMov(pc[i], @truncate(u1, (@as(usize, b ^ i) -% 1) >> 8));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
fn slide(s: [32]u8) [2 * 32 + 1]i8 {
|
||||
var e: [2 * 32 + 1]i8 = undefined;
|
||||
for (s) |x, i| {
|
||||
e[i * 2 + 0] = @as(i8, @truncate(u4, x));
|
||||
e[i * 2 + 1] = @as(i8, @truncate(u4, x >> 4));
|
||||
}
|
||||
// Now, e[0..63] is between 0 and 15, e[63] is between 0 and 7
|
||||
var carry: i8 = 0;
|
||||
for (e[0..64]) |*x| {
|
||||
x.* += carry;
|
||||
carry = (x.* + 8) >> 4;
|
||||
x.* -= carry * 16;
|
||||
std.debug.assert(x.* >= -8 and x.* <= 8);
|
||||
}
|
||||
e[64] = carry;
|
||||
// Now, e[*] is between -8 and 8, including e[64]
|
||||
std.debug.assert(carry >= -8 and carry <= 8);
|
||||
return e;
|
||||
}
|
||||
|
||||
fn pcMul(pc: *const [9]Secp256k1, s: [32]u8, comptime vartime: bool) IdentityElementError!Secp256k1 {
|
||||
std.debug.assert(vartime);
|
||||
const e = slide(s);
|
||||
var q = Secp256k1.identityElement;
|
||||
var pos = e.len - 1;
|
||||
while (true) : (pos -= 1) {
|
||||
const slot = e[pos];
|
||||
if (slot > 0) {
|
||||
q = q.add(pc[@intCast(usize, slot)]);
|
||||
} else if (slot < 0) {
|
||||
q = q.sub(pc[@intCast(usize, -slot)]);
|
||||
}
|
||||
if (pos == 0) break;
|
||||
q = q.dbl().dbl().dbl().dbl();
|
||||
}
|
||||
try q.rejectIdentity();
|
||||
return q;
|
||||
}
|
||||
|
||||
fn pcMul16(pc: *const [16]Secp256k1, s: [32]u8, comptime vartime: bool) IdentityElementError!Secp256k1 {
|
||||
var q = Secp256k1.identityElement;
|
||||
var pos: usize = 252;
|
||||
while (true) : (pos -= 4) {
|
||||
const slot = @truncate(u4, (s[pos >> 3] >> @truncate(u3, pos)));
|
||||
if (vartime) {
|
||||
if (slot != 0) {
|
||||
q = q.add(pc[slot]);
|
||||
}
|
||||
} else {
|
||||
q = q.add(pcSelect(16, pc, slot));
|
||||
}
|
||||
if (pos == 0) break;
|
||||
q = q.dbl().dbl().dbl().dbl();
|
||||
}
|
||||
try q.rejectIdentity();
|
||||
return q;
|
||||
}
|
||||
|
||||
fn precompute(p: Secp256k1, comptime count: usize) [1 + count]Secp256k1 {
|
||||
var pc: [1 + count]Secp256k1 = undefined;
|
||||
pc[0] = Secp256k1.identityElement;
|
||||
pc[1] = p;
|
||||
var i: usize = 2;
|
||||
while (i <= count) : (i += 1) {
|
||||
pc[i] = if (i % 2 == 0) pc[i / 2].dbl() else pc[i - 1].add(p);
|
||||
}
|
||||
return pc;
|
||||
}
|
||||
|
||||
const basePointPc = pc: {
|
||||
@setEvalBranchQuota(50000);
|
||||
break :pc precompute(Secp256k1.basePoint, 15);
|
||||
};
|
||||
|
||||
/// Multiply an elliptic curve point by a scalar.
|
||||
/// Return error.IdentityElement if the result is the identity element.
|
||||
pub fn mul(p: Secp256k1, s_: [32]u8, endian: std.builtin.Endian) IdentityElementError!Secp256k1 {
|
||||
const s = if (endian == .Little) s_ else Fe.orderSwap(s_);
|
||||
if (p.is_base) {
|
||||
return pcMul16(&basePointPc, s, false);
|
||||
}
|
||||
try p.rejectIdentity();
|
||||
const pc = precompute(p, 15);
|
||||
return pcMul16(&pc, s, false);
|
||||
}
|
||||
|
||||
/// Multiply an elliptic curve point by a *PUBLIC* scalar *IN VARIABLE TIME*
|
||||
/// This can be used for signature verification.
|
||||
pub fn mulPublic(p: Secp256k1, s_: [32]u8, endian: std.builtin.Endian) IdentityElementError!Secp256k1 {
|
||||
const s = if (endian == .Little) s_ else Fe.orderSwap(s_);
|
||||
const zero = comptime scalar.Scalar.zero.toBytes(.Little);
|
||||
if (mem.eql(u8, &zero, &s)) {
|
||||
return error.IdentityElement;
|
||||
}
|
||||
const pc = precompute(p, 8);
|
||||
var lambda_p = try pcMul(&pc, Endormorphism.lambda_s, true);
|
||||
var split_scalar = Endormorphism.splitScalar(s, .Little);
|
||||
var px = p;
|
||||
|
||||
// If a key is negative, flip the sign to keep it half-sized,
|
||||
// and flip the sign of the Y point coordinate to compensate.
|
||||
if (split_scalar.r1[split_scalar.r1.len / 2] != 0) {
|
||||
split_scalar.r1 = scalar.neg(split_scalar.r1, .Little) catch zero;
|
||||
px = px.neg();
|
||||
}
|
||||
if (split_scalar.r2[split_scalar.r2.len / 2] != 0) {
|
||||
split_scalar.r2 = scalar.neg(split_scalar.r2, .Little) catch zero;
|
||||
lambda_p = lambda_p.neg();
|
||||
}
|
||||
return mulDoubleBasePublicEndo(px, split_scalar.r1, lambda_p, split_scalar.r2);
|
||||
}
|
||||
|
||||
// Half-size double-base public multiplication when using the curve endomorphism.
|
||||
// Scalars must be in little-endian.
|
||||
// The second point is unlikely to be the generator, so don't even try to use the comptime table for it.
|
||||
fn mulDoubleBasePublicEndo(p1: Secp256k1, s1: [32]u8, p2: Secp256k1, s2: [32]u8) IdentityElementError!Secp256k1 {
|
||||
var pc1_array: [9]Secp256k1 = undefined;
|
||||
const pc1 = if (p1.is_base) basePointPc[0..9] else pc: {
|
||||
pc1_array = precompute(p1, 8);
|
||||
break :pc &pc1_array;
|
||||
};
|
||||
const pc2 = precompute(p2, 8);
|
||||
std.debug.assert(s1[s1.len / 2] == 0);
|
||||
std.debug.assert(s2[s2.len / 2] == 0);
|
||||
const e1 = slide(s1);
|
||||
const e2 = slide(s2);
|
||||
var q = Secp256k1.identityElement;
|
||||
var pos: usize = 2 * 32 / 2; // second half is all zero
|
||||
while (true) : (pos -= 1) {
|
||||
const slot1 = e1[pos];
|
||||
if (slot1 > 0) {
|
||||
q = q.add(pc1[@intCast(usize, slot1)]);
|
||||
} else if (slot1 < 0) {
|
||||
q = q.sub(pc1[@intCast(usize, -slot1)]);
|
||||
}
|
||||
const slot2 = e2[pos];
|
||||
if (slot2 > 0) {
|
||||
q = q.add(pc2[@intCast(usize, slot2)]);
|
||||
} else if (slot2 < 0) {
|
||||
q = q.sub(pc2[@intCast(usize, -slot2)]);
|
||||
}
|
||||
if (pos == 0) break;
|
||||
q = q.dbl().dbl().dbl().dbl();
|
||||
}
|
||||
try q.rejectIdentity();
|
||||
return q;
|
||||
}
|
||||
|
||||
/// Double-base multiplication of public parameters - Compute (p1*s1)+(p2*s2) *IN VARIABLE TIME*
|
||||
/// This can be used for signature verification.
|
||||
pub fn mulDoubleBasePublic(p1: Secp256k1, s1_: [32]u8, p2: Secp256k1, s2_: [32]u8, endian: std.builtin.Endian) IdentityElementError!Secp256k1 {
|
||||
const s1 = if (endian == .Little) s1_ else Fe.orderSwap(s1_);
|
||||
const s2 = if (endian == .Little) s2_ else Fe.orderSwap(s2_);
|
||||
try p1.rejectIdentity();
|
||||
var pc1_array: [9]Secp256k1 = undefined;
|
||||
const pc1 = if (p1.is_base) basePointPc[0..9] else pc: {
|
||||
pc1_array = precompute(p1, 8);
|
||||
break :pc &pc1_array;
|
||||
};
|
||||
try p2.rejectIdentity();
|
||||
var pc2_array: [9]Secp256k1 = undefined;
|
||||
const pc2 = if (p2.is_base) basePointPc[0..9] else pc: {
|
||||
pc2_array = precompute(p2, 8);
|
||||
break :pc &pc2_array;
|
||||
};
|
||||
const e1 = slide(s1);
|
||||
const e2 = slide(s2);
|
||||
var q = Secp256k1.identityElement;
|
||||
var pos: usize = 2 * 32;
|
||||
while (true) : (pos -= 1) {
|
||||
const slot1 = e1[pos];
|
||||
if (slot1 > 0) {
|
||||
q = q.add(pc1[@intCast(usize, slot1)]);
|
||||
} else if (slot1 < 0) {
|
||||
q = q.sub(pc1[@intCast(usize, -slot1)]);
|
||||
}
|
||||
const slot2 = e2[pos];
|
||||
if (slot2 > 0) {
|
||||
q = q.add(pc2[@intCast(usize, slot2)]);
|
||||
} else if (slot2 < 0) {
|
||||
q = q.sub(pc2[@intCast(usize, -slot2)]);
|
||||
}
|
||||
if (pos == 0) break;
|
||||
q = q.dbl().dbl().dbl().dbl();
|
||||
}
|
||||
try q.rejectIdentity();
|
||||
return q;
|
||||
}
|
||||
};
|
||||
|
||||
/// A point in affine coordinates.
|
||||
pub const AffineCoordinates = struct {
|
||||
x: Secp256k1.Fe,
|
||||
y: Secp256k1.Fe,
|
||||
|
||||
/// Identity element in affine coordinates.
|
||||
pub const identityElement = AffineCoordinates{ .x = Secp256k1.identityElement.x, .y = Secp256k1.identityElement.y };
|
||||
|
||||
fn cMov(p: *AffineCoordinates, a: AffineCoordinates, c: u1) void {
|
||||
p.x.cMov(a.x, c);
|
||||
p.y.cMov(a.y, c);
|
||||
}
|
||||
};
|
||||
|
||||
test "secp256k1" {
|
||||
_ = @import("tests/secp256k1.zig");
|
||||
}
|
||||
12
lib/std/crypto/pcurves/secp256k1/field.zig
Normal file
12
lib/std/crypto/pcurves/secp256k1/field.zig
Normal file
@ -0,0 +1,12 @@
|
||||
const std = @import("std");
|
||||
const common = @import("../common.zig");
|
||||
|
||||
const Field = common.Field;
|
||||
|
||||
pub const Fe = Field(.{
|
||||
.fiat = @import("secp256k1_64.zig"),
|
||||
.field_order = 115792089237316195423570985008687907853269984665640564039457584007908834671663,
|
||||
.field_bits = 256,
|
||||
.saturated_bits = 256,
|
||||
.encoded_length = 32,
|
||||
});
|
||||
227
lib/std/crypto/pcurves/secp256k1/scalar.zig
Normal file
227
lib/std/crypto/pcurves/secp256k1/scalar.zig
Normal file
@ -0,0 +1,227 @@
|
||||
const std = @import("std");
|
||||
const common = @import("../common.zig");
|
||||
const crypto = std.crypto;
|
||||
const debug = std.debug;
|
||||
const math = std.math;
|
||||
const mem = std.mem;
|
||||
|
||||
const Field = common.Field;
|
||||
|
||||
const NonCanonicalError = std.crypto.errors.NonCanonicalError;
|
||||
const NotSquareError = std.crypto.errors.NotSquareError;
|
||||
|
||||
/// Number of bytes required to encode a scalar.
|
||||
pub const encoded_length = 32;
|
||||
|
||||
/// A compressed scalar, in canonical form.
|
||||
pub const CompressedScalar = [encoded_length]u8;
|
||||
|
||||
const Fe = Field(.{
|
||||
.fiat = @import("secp256k1_scalar_64.zig"),
|
||||
.field_order = 115792089237316195423570985008687907852837564279074904382605163141518161494337,
|
||||
.field_bits = 256,
|
||||
.saturated_bits = 256,
|
||||
.encoded_length = encoded_length,
|
||||
});
|
||||
|
||||
/// The scalar field order.
|
||||
pub const field_order = Fe.field_order;
|
||||
|
||||
/// Reject a scalar whose encoding is not canonical.
|
||||
pub fn rejectNonCanonical(s: CompressedScalar, endian: std.builtin.Endian) NonCanonicalError!void {
|
||||
return Fe.rejectNonCanonical(s, endian);
|
||||
}
|
||||
|
||||
/// Reduce a 48-bytes scalar to the field size.
|
||||
pub fn reduce48(s: [48]u8, endian: std.builtin.Endian) CompressedScalar {
|
||||
return Scalar.fromBytes48(s, endian).toBytes(endian);
|
||||
}
|
||||
|
||||
/// Reduce a 64-bytes scalar to the field size.
|
||||
pub fn reduce64(s: [64]u8, endian: std.builtin.Endian) CompressedScalar {
|
||||
return ScalarDouble.fromBytes64(s, endian).toBytes(endian);
|
||||
}
|
||||
|
||||
/// Return a*b (mod L)
|
||||
pub fn mul(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian) NonCanonicalError!CompressedScalar {
|
||||
return (try Scalar.fromBytes(a, endian)).mul(try Scalar.fromBytes(b, endian)).toBytes(endian);
|
||||
}
|
||||
|
||||
/// Return a*b+c (mod L)
|
||||
pub fn mulAdd(a: CompressedScalar, b: CompressedScalar, c: CompressedScalar, endian: std.builtin.Endian) NonCanonicalError!CompressedScalar {
|
||||
return (try Scalar.fromBytes(a, endian)).mul(try Scalar.fromBytes(b, endian)).add(try Scalar.fromBytes(c, endian)).toBytes(endian);
|
||||
}
|
||||
|
||||
/// Return a+b (mod L)
|
||||
pub fn add(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian) NonCanonicalError!CompressedScalar {
|
||||
return (try Scalar.fromBytes(a, endian)).add(try Scalar.fromBytes(b, endian)).toBytes(endian);
|
||||
}
|
||||
|
||||
/// Return -s (mod L)
|
||||
pub fn neg(s: CompressedScalar, endian: std.builtin.Endian) NonCanonicalError!CompressedScalar {
|
||||
return (try Scalar.fromBytes(s, endian)).neg().toBytes(endian);
|
||||
}
|
||||
|
||||
/// Return (a-b) (mod L)
|
||||
pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian) NonCanonicalError!CompressedScalar {
|
||||
return (try Scalar.fromBytes(a, endian)).sub(try Scalar.fromBytes(b, endian)).toBytes(endian);
|
||||
}
|
||||
|
||||
/// Return a random scalar
|
||||
pub fn random(endian: std.builtin.Endian) CompressedScalar {
|
||||
return Scalar.random().toBytes(endian);
|
||||
}
|
||||
|
||||
/// A scalar in unpacked representation.
|
||||
pub const Scalar = struct {
|
||||
fe: Fe,
|
||||
|
||||
/// Zero.
|
||||
pub const zero = Scalar{ .fe = Fe.zero };
|
||||
|
||||
/// One.
|
||||
pub const one = Scalar{ .fe = Fe.one };
|
||||
|
||||
/// Unpack a serialized representation of a scalar.
|
||||
pub fn fromBytes(s: CompressedScalar, endian: std.builtin.Endian) NonCanonicalError!Scalar {
|
||||
return Scalar{ .fe = try Fe.fromBytes(s, endian) };
|
||||
}
|
||||
|
||||
/// Reduce a 384 bit input to the field size.
|
||||
pub fn fromBytes48(s: [48]u8, endian: std.builtin.Endian) Scalar {
|
||||
const t = ScalarDouble.fromBytes(384, s, endian);
|
||||
return t.reduce(384);
|
||||
}
|
||||
|
||||
/// Reduce a 512 bit input to the field size.
|
||||
pub fn fromBytes64(s: [64]u8, endian: std.builtin.Endian) Scalar {
|
||||
const t = ScalarDouble.fromBytes(512, s, endian);
|
||||
return t.reduce(512);
|
||||
}
|
||||
|
||||
/// Pack a scalar into bytes.
|
||||
pub fn toBytes(n: Scalar, endian: std.builtin.Endian) CompressedScalar {
|
||||
return n.fe.toBytes(endian);
|
||||
}
|
||||
|
||||
/// Return true if the scalar is zero..
|
||||
pub fn isZero(n: Scalar) bool {
|
||||
return n.fe.isZero();
|
||||
}
|
||||
|
||||
/// Return true if a and b are equivalent.
|
||||
pub fn equivalent(a: Scalar, b: Scalar) bool {
|
||||
return a.fe.equivalent(b.fe);
|
||||
}
|
||||
|
||||
/// Compute x+y (mod L)
|
||||
pub fn add(x: Scalar, y: Scalar) Scalar {
|
||||
return Scalar{ .fe = x.fe.add(y.fe) };
|
||||
}
|
||||
|
||||
/// Compute x-y (mod L)
|
||||
pub fn sub(x: Scalar, y: Scalar) Scalar {
|
||||
return Scalar{ .fe = x.fe.sub(y.fe) };
|
||||
}
|
||||
|
||||
/// Compute 2n (mod L)
|
||||
pub fn dbl(n: Scalar) Scalar {
|
||||
return Scalar{ .fe = n.fe.dbl() };
|
||||
}
|
||||
|
||||
/// Compute x*y (mod L)
|
||||
pub fn mul(x: Scalar, y: Scalar) Scalar {
|
||||
return Scalar{ .fe = x.fe.mul(y.fe) };
|
||||
}
|
||||
|
||||
/// Compute x^2 (mod L)
|
||||
pub fn sq(n: Scalar) Scalar {
|
||||
return Scalar{ .fe = n.fe.sq() };
|
||||
}
|
||||
|
||||
/// Compute x^n (mod L)
|
||||
pub fn pow(a: Scalar, comptime T: type, comptime n: T) Scalar {
|
||||
return Scalar{ .fe = a.fe.pow(n) };
|
||||
}
|
||||
|
||||
/// Compute -x (mod L)
|
||||
pub fn neg(n: Scalar) Scalar {
|
||||
return Scalar{ .fe = n.fe.neg() };
|
||||
}
|
||||
|
||||
/// Compute x^-1 (mod L)
|
||||
pub fn invert(n: Scalar) Scalar {
|
||||
return Scalar{ .fe = n.fe.invert() };
|
||||
}
|
||||
|
||||
/// Return true if n is a quadratic residue mod L.
|
||||
pub fn isSquare(n: Scalar) Scalar {
|
||||
return n.fe.isSquare();
|
||||
}
|
||||
|
||||
/// Return the square root of L, or NotSquare if there isn't any solutions.
|
||||
pub fn sqrt(n: Scalar) NotSquareError!Scalar {
|
||||
return Scalar{ .fe = try n.fe.sqrt() };
|
||||
}
|
||||
|
||||
/// Return a random scalar < L.
|
||||
pub fn random() Scalar {
|
||||
var s: [48]u8 = undefined;
|
||||
while (true) {
|
||||
crypto.random.bytes(&s);
|
||||
const n = Scalar.fromBytes48(s, .Little);
|
||||
if (!n.isZero()) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ScalarDouble = struct {
|
||||
x1: Fe,
|
||||
x2: Fe,
|
||||
x3: Fe,
|
||||
|
||||
fn fromBytes(comptime bits: usize, s_: [bits / 8]u8, endian: std.builtin.Endian) ScalarDouble {
|
||||
debug.assert(bits > 0 and bits <= 512 and bits >= Fe.saturated_bits and bits <= Fe.saturated_bits * 3);
|
||||
|
||||
var s = s_;
|
||||
if (endian == .Big) {
|
||||
for (s_) |x, i| s[s.len - 1 - i] = x;
|
||||
}
|
||||
var t = ScalarDouble{ .x1 = undefined, .x2 = Fe.zero, .x3 = Fe.zero };
|
||||
{
|
||||
var b = [_]u8{0} ** encoded_length;
|
||||
const len = math.min(s.len, 24);
|
||||
mem.copy(u8, b[0..len], s[0..len]);
|
||||
t.x1 = Fe.fromBytes(b, .Little) catch unreachable;
|
||||
}
|
||||
if (s_.len >= 24) {
|
||||
var b = [_]u8{0} ** encoded_length;
|
||||
const len = math.min(s.len - 24, 24);
|
||||
mem.copy(u8, b[0..len], s[24..][0..len]);
|
||||
t.x2 = Fe.fromBytes(b, .Little) catch unreachable;
|
||||
}
|
||||
if (s_.len >= 48) {
|
||||
var b = [_]u8{0} ** encoded_length;
|
||||
const len = s.len - 48;
|
||||
mem.copy(u8, b[0..len], s[48..][0..len]);
|
||||
t.x3 = Fe.fromBytes(b, .Little) catch unreachable;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
fn reduce(expanded: ScalarDouble, comptime bits: usize) Scalar {
|
||||
debug.assert(bits > 0 and bits <= Fe.saturated_bits * 3 and bits <= 512);
|
||||
var fe = expanded.x1;
|
||||
if (bits >= 192) {
|
||||
const st1 = Fe.fromInt(1 << 192) catch unreachable;
|
||||
fe = fe.add(expanded.x2.mul(st1));
|
||||
if (bits >= 384) {
|
||||
const st2 = st1.sq();
|
||||
fe = fe.add(expanded.x3.mul(st2));
|
||||
}
|
||||
}
|
||||
return Scalar{ .fe = fe };
|
||||
}
|
||||
};
|
||||
1964
lib/std/crypto/pcurves/secp256k1/secp256k1_64.zig
Normal file
1964
lib/std/crypto/pcurves/secp256k1/secp256k1_64.zig
Normal file
File diff suppressed because it is too large
Load Diff
2024
lib/std/crypto/pcurves/secp256k1/secp256k1_scalar_64.zig
Normal file
2024
lib/std/crypto/pcurves/secp256k1/secp256k1_scalar_64.zig
Normal file
File diff suppressed because it is too large
Load Diff
137
lib/std/crypto/pcurves/tests/secp256k1.zig
Normal file
137
lib/std/crypto/pcurves/tests/secp256k1.zig
Normal file
@ -0,0 +1,137 @@
|
||||
const std = @import("std");
|
||||
const fmt = std.fmt;
|
||||
const testing = std.testing;
|
||||
|
||||
const Secp256k1 = @import("../secp256k1.zig").Secp256k1;
|
||||
|
||||
test "secp256k1 ECDH key exchange" {
|
||||
const dha = Secp256k1.scalar.random(.Little);
|
||||
const dhb = Secp256k1.scalar.random(.Little);
|
||||
const dhA = try Secp256k1.basePoint.mul(dha, .Little);
|
||||
const dhB = try Secp256k1.basePoint.mul(dhb, .Little);
|
||||
const shareda = try dhA.mul(dhb, .Little);
|
||||
const sharedb = try dhB.mul(dha, .Little);
|
||||
try testing.expect(shareda.equivalent(sharedb));
|
||||
}
|
||||
|
||||
test "secp256k1 ECDH key exchange including public multiplication" {
|
||||
const dha = Secp256k1.scalar.random(.Little);
|
||||
const dhb = Secp256k1.scalar.random(.Little);
|
||||
const dhA = try Secp256k1.basePoint.mul(dha, .Little);
|
||||
const dhB = try Secp256k1.basePoint.mulPublic(dhb, .Little);
|
||||
const shareda = try dhA.mul(dhb, .Little);
|
||||
const sharedb = try dhB.mulPublic(dha, .Little);
|
||||
try testing.expect(shareda.equivalent(sharedb));
|
||||
}
|
||||
|
||||
test "secp256k1 point from affine coordinates" {
|
||||
const xh = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
|
||||
const yh = "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8";
|
||||
var xs: [32]u8 = undefined;
|
||||
_ = try fmt.hexToBytes(&xs, xh);
|
||||
var ys: [32]u8 = undefined;
|
||||
_ = try fmt.hexToBytes(&ys, yh);
|
||||
var p = try Secp256k1.fromSerializedAffineCoordinates(xs, ys, .Big);
|
||||
try testing.expect(p.equivalent(Secp256k1.basePoint));
|
||||
}
|
||||
|
||||
test "secp256k1 test vectors" {
|
||||
const expected = [_][]const u8{
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||
"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
|
||||
"f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
|
||||
"e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13",
|
||||
"2f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4",
|
||||
"fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556",
|
||||
"5cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc",
|
||||
"2f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01",
|
||||
"acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe",
|
||||
};
|
||||
var p = Secp256k1.identityElement;
|
||||
for (expected) |xh| {
|
||||
const x = p.affineCoordinates().x;
|
||||
p = p.add(Secp256k1.basePoint);
|
||||
var xs: [32]u8 = undefined;
|
||||
_ = try fmt.hexToBytes(&xs, xh);
|
||||
try testing.expectEqualSlices(u8, &x.toBytes(.Big), &xs);
|
||||
}
|
||||
}
|
||||
|
||||
test "secp256k1 test vectors - doubling" {
|
||||
const expected = [_][]const u8{
|
||||
"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||
"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
|
||||
"e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13",
|
||||
"2f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01",
|
||||
"e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a",
|
||||
};
|
||||
var p = Secp256k1.basePoint;
|
||||
for (expected) |xh| {
|
||||
const x = p.affineCoordinates().x;
|
||||
p = p.dbl();
|
||||
var xs: [32]u8 = undefined;
|
||||
_ = try fmt.hexToBytes(&xs, xh);
|
||||
try testing.expectEqualSlices(u8, &x.toBytes(.Big), &xs);
|
||||
}
|
||||
}
|
||||
|
||||
test "secp256k1 compressed sec1 encoding/decoding" {
|
||||
const p = Secp256k1.random();
|
||||
const s = p.toCompressedSec1();
|
||||
const q = try Secp256k1.fromSec1(&s);
|
||||
try testing.expect(p.equivalent(q));
|
||||
}
|
||||
|
||||
test "secp256k1 uncompressed sec1 encoding/decoding" {
|
||||
const p = Secp256k1.random();
|
||||
const s = p.toUncompressedSec1();
|
||||
const q = try Secp256k1.fromSec1(&s);
|
||||
try testing.expect(p.equivalent(q));
|
||||
}
|
||||
|
||||
test "secp256k1 public key is the neutral element" {
|
||||
const n = Secp256k1.scalar.Scalar.zero.toBytes(.Little);
|
||||
const p = Secp256k1.random();
|
||||
try testing.expectError(error.IdentityElement, p.mul(n, .Little));
|
||||
}
|
||||
|
||||
test "secp256k1 public key is the neutral element (public verification)" {
|
||||
const n = Secp256k1.scalar.Scalar.zero.toBytes(.Little);
|
||||
const p = Secp256k1.random();
|
||||
try testing.expectError(error.IdentityElement, p.mulPublic(n, .Little));
|
||||
}
|
||||
|
||||
test "secp256k1 field element non-canonical encoding" {
|
||||
const s = [_]u8{0xff} ** 32;
|
||||
try testing.expectError(error.NonCanonical, Secp256k1.Fe.fromBytes(s, .Little));
|
||||
}
|
||||
|
||||
test "secp256k1 neutral element decoding" {
|
||||
try testing.expectError(error.InvalidEncoding, Secp256k1.fromAffineCoordinates(.{ .x = Secp256k1.Fe.zero, .y = Secp256k1.Fe.zero }));
|
||||
const p = try Secp256k1.fromAffineCoordinates(.{ .x = Secp256k1.Fe.zero, .y = Secp256k1.Fe.one });
|
||||
try testing.expectError(error.IdentityElement, p.rejectIdentity());
|
||||
}
|
||||
|
||||
test "secp256k1 double base multiplication" {
|
||||
const p1 = Secp256k1.basePoint;
|
||||
const p2 = Secp256k1.basePoint.dbl();
|
||||
const s1 = [_]u8{0x01} ** 32;
|
||||
const s2 = [_]u8{0x02} ** 32;
|
||||
const pr1 = try Secp256k1.mulDoubleBasePublic(p1, s1, p2, s2, .Little);
|
||||
const pr2 = (try p1.mul(s1, .Little)).add(try p2.mul(s2, .Little));
|
||||
try testing.expect(pr1.equivalent(pr2));
|
||||
}
|
||||
|
||||
test "secp256k1 scalar inverse" {
|
||||
const expected = "08d0684a0fe8ea978b68a29e4b4ffdbd19eeb59db25301cf23ecbe568e1f9822";
|
||||
var out: [32]u8 = undefined;
|
||||
_ = try std.fmt.hexToBytes(&out, expected);
|
||||
|
||||
const scalar = try Secp256k1.scalar.Scalar.fromBytes(.{
|
||||
0x94, 0xa1, 0xbb, 0xb1, 0x4b, 0x90, 0x6a, 0x61, 0xa2, 0x80, 0xf2, 0x45, 0xf9, 0xe9, 0x3c, 0x7f,
|
||||
0x3b, 0x4a, 0x62, 0x47, 0x82, 0x4f, 0x5d, 0x33, 0xb9, 0x67, 0x07, 0x87, 0x64, 0x2a, 0x68, 0xde,
|
||||
}, .Big);
|
||||
const inverse = scalar.invert();
|
||||
try std.testing.expectEqualSlices(u8, &out, &inverse.toBytes(.Big));
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user