| ... | ... | @@ -35,15 +35,17 @@ pub const Secp256k1 = struct { |
| 35 | 35 | |
| 36 | 36 | pub const B = Fe.fromInt(7) catch unreachable; |
| 37 | 37 | |
| 38 | | pub const Endormorphism = struct { |
| 39 | | const lambda: u256 = 37718080363155996902926221483475020450927657555482586988616620542887997980018; |
| 40 | | const beta: u256 = 55594575648329892869085402983802832744385952214688224221778511981742606582254; |
| 38 | pub const Endomorphism = struct { |
| 39 | const lambda = scalarFromInt(37718080363155996902926221483475020450927657555482586988616620542887997980018); |
| 40 | const beta = Fe.fromInt(55594575648329892869085402983802832744385952214688224221778511981742606582254) catch unreachable; |
| 41 | const b1_neg = scalarFromInt(303414439467246543595250775667605759171); |
| 42 | const b2_neg = scalarFromInt(scalar.field_order - 64502973549206556628585045361533709077); |
| 41 | 43 | |
| 42 | | const lambda_s = s: { |
| 44 | fn scalarFromInt(x: u256) scalar.Scalar { |
| 43 | 45 | var buf: [32]u8 = undefined; |
| 44 | | mem.writeInt(u256, &buf, Endormorphism.lambda, .little); |
| 45 | | break :s buf; |
| 46 | | }; |
| 46 | mem.writeInt(u256, &buf, x, .little); |
| 47 | return scalar.Scalar.fromBytes(buf, .little) catch unreachable; |
| 48 | } |
| 47 | 49 | |
| 48 | 50 | pub const SplitScalar = struct { |
| 49 | 51 | r1: [32]u8, |
| ... | ... | @@ -51,39 +53,21 @@ pub const Secp256k1 = struct { |
| 51 | 53 | }; |
| 52 | 54 | |
| 53 | 55 | /// Compute r1 and r2 so that k = r1 + r2*lambda (mod L). |
| 56 | /// Both outputs use the requested byte order. |
| 54 | 57 | pub fn splitScalar(s: [32]u8, endian: std.lang.Endian) NonCanonicalError!SplitScalar { |
| 55 | | const b1_neg_s = comptime s: { |
| 56 | | var buf: [32]u8 = undefined; |
| 57 | | mem.writeInt(u256, &buf, 303414439467246543595250775667605759171, .little); |
| 58 | | break :s buf; |
| 59 | | }; |
| 60 | | const b2_neg_s = comptime s: { |
| 61 | | var buf: [32]u8 = undefined; |
| 62 | | mem.writeInt(u256, &buf, scalar.field_order - 64502973549206556628585045361533709077, .little); |
| 63 | | break :s buf; |
| 64 | | }; |
| 65 | | const k = mem.readInt(u256, &s, endian); |
| 66 | | |
| 67 | | const t1 = math.mulWide(u256, k, 21949224512762693861512883645436906316123769664773102907882521278123970637873); |
| 68 | | const t2 = math.mulWide(u256, k, 103246583619904461035481197785446227098457807945486720222659797044629401272177); |
| 58 | const k = try scalar.Scalar.fromBytes(s, endian); |
| 59 | const k_int = mem.readInt(u256, &s, endian); |
| 60 | |
| 61 | const t1 = math.mulWide(u256, k_int, 21949224512762693861512883645436906316123769664773102907882521278123970637873); |
| 62 | const t2 = math.mulWide(u256, k_int, 103246583619904461035481197785446227098457807945486720222659797044629401272177); |
| 69 | 63 | |
| 70 | 64 | const c1 = @as(u128, @truncate(t1 >> 384)) + @as(u1, @truncate(t1 >> 383)); |
| 71 | 65 | const c2 = @as(u128, @truncate(t2 >> 384)) + @as(u1, @truncate(t2 >> 383)); |
| 72 | 66 | |
| 73 | | var buf: [32]u8 = undefined; |
| 74 | | |
| 75 | | mem.writeInt(u256, &buf, c1, .little); |
| 76 | | const c1x = try scalar.mul(buf, b1_neg_s, .little); |
| 77 | | |
| 78 | | mem.writeInt(u256, &buf, c2, .little); |
| 79 | | const c2x = try scalar.mul(buf, b2_neg_s, .little); |
| 67 | const r2 = scalarFromInt(c1).mul(b1_neg).add(scalarFromInt(c2).mul(b2_neg)); |
| 68 | const r1 = k.sub(r2.mul(lambda)); |
| 80 | 69 | |
| 81 | | const r2 = try scalar.add(c1x, c2x, .little); |
| 82 | | |
| 83 | | var r1 = try scalar.mul(r2, lambda_s, .little); |
| 84 | | r1 = try scalar.sub(s, r1, .little); |
| 85 | | |
| 86 | | return SplitScalar{ .r1 = r1, .r2 = r2 }; |
| 70 | return .{ .r1 = r1.toBytes(endian), .r2 = r2.toBytes(endian) }; |
| 87 | 71 | } |
| 88 | 72 | }; |
| 89 | 73 | |
| ... | ... | @@ -367,17 +351,43 @@ pub const Secp256k1 = struct { |
| 367 | 351 | return e; |
| 368 | 352 | } |
| 369 | 353 | |
| 370 | | fn pcMul(pc: *const [9]Secp256k1, s: [32]u8, comptime vartime: bool) IdentityElementError!Secp256k1 { |
| 371 | | std.debug.assert(vartime); |
| 372 | | const e = slide(s); |
| 354 | // Compute the sum of s[i]*p[i] *IN VARIABLE TIME*. Scalars can be larger than the group order. |
| 355 | fn mulMultiPublic(comptime n: usize, p: [n]Secp256k1, s: [n][32]u8, endian: std.lang.Endian) IdentityElementError!Secp256k1 { |
| 356 | var pcs: [2 * n][9]Secp256k1 = undefined; |
| 357 | var es: [2 * n][2 * 32 + 1]i8 = undefined; |
| 358 | for (p, s, 0..) |point, k, i| { |
| 359 | pcs[2 * i] = if (point.is_base) basePointPc[0..9].* else precompute(point, 8); |
| 360 | // lambda*P |
| 361 | for (&pcs[2 * i + 1], pcs[2 * i]) |*pc_lambda, pc| { |
| 362 | pc_lambda.* = .{ .x = pc.x.mul(Endomorphism.beta), .y = pc.y, .z = pc.z }; |
| 363 | } |
| 364 | var s48: [48]u8 = undefined; |
| 365 | mem.writeInt(u384, &s48, mem.readInt(u256, &k, endian), .little); |
| 366 | const split = Endomorphism.splitScalar(scalar.reduce48(s48, .little), .little) catch unreachable; |
| 367 | |
| 368 | for ([_][32]u8{ split.r1, split.r2 }, es[2 * i ..][0..2]) |r, *e| { |
| 369 | const x = mem.readInt(u256, &r, .little); |
| 370 | const negate = x > scalar.field_order / 2; |
| 371 | var r_abs: [32]u8 = undefined; |
| 372 | mem.writeInt(u256, &r_abs, if (negate) scalar.field_order - x else x, .little); |
| 373 | e.* = slide(r_abs); |
| 374 | if (negate) { |
| 375 | for (e) |*d| d.* = -d.*; |
| 376 | } |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | var pos: usize = 0; |
| 381 | for (&es) |*e| pos = @max(pos, mem.findLastNone(i8, e, &.{0}) orelse 0); |
| 373 | 382 | var q = Secp256k1.identityElement; |
| 374 | | var pos = e.len - 1; |
| 375 | 383 | while (true) : (pos -= 1) { |
| 376 | | const slot = e[pos]; |
| 377 | | if (slot > 0) { |
| 378 | | q = q.add(pc[@as(usize, @intCast(slot))]); |
| 379 | | } else if (slot < 0) { |
| 380 | | q = q.sub(pc[@as(usize, @intCast(-slot))]); |
| 384 | for (&pcs, &es) |*pc, *e| { |
| 385 | const slot = e[pos]; |
| 386 | if (slot > 0) { |
| 387 | q = q.add(pc[@as(usize, @intCast(slot))]); |
| 388 | } else if (slot < 0) { |
| 389 | q = q.sub(pc[@as(usize, @intCast(-slot))]); |
| 390 | } |
| 381 | 391 | } |
| 382 | 392 | if (pos == 0) break; |
| 383 | 393 | q = q.dbl().dbl().dbl().dbl(); |
| ... | ... | @@ -435,105 +445,17 @@ pub const Secp256k1 = struct { |
| 435 | 445 | |
| 436 | 446 | /// Multiply an elliptic curve point by a *PUBLIC* scalar *IN VARIABLE TIME* |
| 437 | 447 | /// This can be used for signature verification. |
| 438 | | pub fn mulPublic(p: Secp256k1, s_: [32]u8, endian: std.lang.Endian) (IdentityElementError || NonCanonicalError)!Secp256k1 { |
| 439 | | const s = if (endian == .little) s_ else Fe.orderSwap(s_); |
| 440 | | const zero = comptime scalar.Scalar.zero.toBytes(.little); |
| 441 | | if (mem.eql(u8, &zero, &s)) { |
| 442 | | return error.IdentityElement; |
| 443 | | } |
| 444 | | const pc = precompute(p, 8); |
| 445 | | var lambda_p = try pcMul(&pc, Endormorphism.lambda_s, true); |
| 446 | | var split_scalar = try Endormorphism.splitScalar(s, .little); |
| 447 | | var px = p; |
| 448 | | |
| 449 | | // If a key is negative, flip the sign to keep it half-sized, |
| 450 | | // and flip the sign of the Y point coordinate to compensate. |
| 451 | | if (split_scalar.r1[split_scalar.r1.len / 2] != 0) { |
| 452 | | split_scalar.r1 = scalar.neg(split_scalar.r1, .little) catch zero; |
| 453 | | px = px.neg(); |
| 454 | | } |
| 455 | | if (split_scalar.r2[split_scalar.r2.len / 2] != 0) { |
| 456 | | split_scalar.r2 = scalar.neg(split_scalar.r2, .little) catch zero; |
| 457 | | lambda_p = lambda_p.neg(); |
| 458 | | } |
| 459 | | return mulDoubleBasePublicEndo(px, split_scalar.r1, lambda_p, split_scalar.r2); |
| 460 | | } |
| 461 | | |
| 462 | | // Half-size double-base public multiplication when using the curve endomorphism. |
| 463 | | // Scalars must be in little-endian. |
| 464 | | // The second point is unlikely to be the generator, so don't even try to use the comptime table for it. |
| 465 | | fn mulDoubleBasePublicEndo(p1: Secp256k1, s1: [32]u8, p2: Secp256k1, s2: [32]u8) IdentityElementError!Secp256k1 { |
| 466 | | var pc1_array: [9]Secp256k1 = undefined; |
| 467 | | const pc1 = if (p1.is_base) basePointPc[0..9] else pc: { |
| 468 | | pc1_array = precompute(p1, 8); |
| 469 | | break :pc &pc1_array; |
| 470 | | }; |
| 471 | | const pc2 = precompute(p2, 8); |
| 472 | | std.debug.assert(s1[s1.len / 2] == 0); |
| 473 | | std.debug.assert(s2[s2.len / 2] == 0); |
| 474 | | const e1 = slide(s1); |
| 475 | | const e2 = slide(s2); |
| 476 | | var q = Secp256k1.identityElement; |
| 477 | | var pos: usize = 2 * 32 / 2; // second half is all zero |
| 478 | | while (true) : (pos -= 1) { |
| 479 | | const slot1 = e1[pos]; |
| 480 | | if (slot1 > 0) { |
| 481 | | q = q.add(pc1[@as(usize, @intCast(slot1))]); |
| 482 | | } else if (slot1 < 0) { |
| 483 | | q = q.sub(pc1[@as(usize, @intCast(-slot1))]); |
| 484 | | } |
| 485 | | const slot2 = e2[pos]; |
| 486 | | if (slot2 > 0) { |
| 487 | | q = q.add(pc2[@as(usize, @intCast(slot2))]); |
| 488 | | } else if (slot2 < 0) { |
| 489 | | q = q.sub(pc2[@as(usize, @intCast(-slot2))]); |
| 490 | | } |
| 491 | | if (pos == 0) break; |
| 492 | | q = q.dbl().dbl().dbl().dbl(); |
| 493 | | } |
| 494 | | try q.rejectIdentity(); |
| 495 | | return q; |
| 448 | pub fn mulPublic(p: Secp256k1, s: [32]u8, endian: std.lang.Endian) (IdentityElementError || NonCanonicalError)!Secp256k1 { |
| 449 | try scalar.rejectNonCanonical(s, endian); |
| 450 | return mulMultiPublic(1, .{p}, .{s}, endian); |
| 496 | 451 | } |
| 497 | 452 | |
| 498 | 453 | /// Double-base multiplication of public parameters - Compute (p1*s1)+(p2*s2) *IN VARIABLE TIME* |
| 499 | 454 | /// This can be used for signature verification. |
| 500 | | pub fn mulDoubleBasePublic(p1: Secp256k1, s1_: [32]u8, p2: Secp256k1, s2_: [32]u8, endian: std.lang.Endian) IdentityElementError!Secp256k1 { |
| 501 | | const s1 = if (endian == .little) s1_ else Fe.orderSwap(s1_); |
| 502 | | const s2 = if (endian == .little) s2_ else Fe.orderSwap(s2_); |
| 455 | pub fn mulDoubleBasePublic(p1: Secp256k1, s1: [32]u8, p2: Secp256k1, s2: [32]u8, endian: std.lang.Endian) IdentityElementError!Secp256k1 { |
| 503 | 456 | try p1.rejectIdentity(); |
| 504 | | var pc1_array: [9]Secp256k1 = undefined; |
| 505 | | const pc1 = if (p1.is_base) basePointPc[0..9] else pc: { |
| 506 | | pc1_array = precompute(p1, 8); |
| 507 | | break :pc &pc1_array; |
| 508 | | }; |
| 509 | 457 | try p2.rejectIdentity(); |
| 510 | | var pc2_array: [9]Secp256k1 = undefined; |
| 511 | | const pc2 = if (p2.is_base) basePointPc[0..9] else pc: { |
| 512 | | pc2_array = precompute(p2, 8); |
| 513 | | break :pc &pc2_array; |
| 514 | | }; |
| 515 | | const e1 = slide(s1); |
| 516 | | const e2 = slide(s2); |
| 517 | | var q = Secp256k1.identityElement; |
| 518 | | var pos: usize = 2 * 32; |
| 519 | | while (true) : (pos -= 1) { |
| 520 | | const slot1 = e1[pos]; |
| 521 | | if (slot1 > 0) { |
| 522 | | q = q.add(pc1[@as(usize, @intCast(slot1))]); |
| 523 | | } else if (slot1 < 0) { |
| 524 | | q = q.sub(pc1[@as(usize, @intCast(-slot1))]); |
| 525 | | } |
| 526 | | const slot2 = e2[pos]; |
| 527 | | if (slot2 > 0) { |
| 528 | | q = q.add(pc2[@as(usize, @intCast(slot2))]); |
| 529 | | } else if (slot2 < 0) { |
| 530 | | q = q.sub(pc2[@as(usize, @intCast(-slot2))]); |
| 531 | | } |
| 532 | | if (pos == 0) break; |
| 533 | | q = q.dbl().dbl().dbl().dbl(); |
| 534 | | } |
| 535 | | try q.rejectIdentity(); |
| 536 | | return q; |
| 458 | return mulMultiPublic(2, .{ p1, p2 }, .{ s1, s2 }, endian); |
| 537 | 459 | } |
| 538 | 460 | }; |
| 539 | 461 | |