simultaneous multiplication for triplets based on Straus-Shamir Trick

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2025-10-13 19:58:28 +03:00
parent 2b3eb14012
commit 901e969877
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
4 changed files with 232 additions and 26 deletions

View File

@ -32,30 +32,36 @@ For full background and protocol details see the [project wiki](https://git.ghos
`Jacobian` is the original implementation used as a reference implementation, while `Projective` is optimized one.
```
╭----------------------------------------+-----------------+-------+--------+-------+---------╮
| src/MathTester.sol:MathTester Contract | | | | | |
+=============================================================================================+
| Deployment Cost | Deployment Size | | | | |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| 1065323 | 4715 | | | | |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| | | | | | |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| addJacobian | 1768 | 1768 | 1768 | 1768 | 44 |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| addProjective | 992 | 992 | 992 | 992 | 44 |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| doubleJacobian | 777 | 777 | 777 | 777 | 45 |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| doubleProjective | 532 | 532 | 532 | 532 | 45 |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| toAffineJacobian | 30564 | 36206 | 36300 | 40841 | 89 |
|----------------------------------------+-----------------+-------+--------+-------+---------|
| toAffineProjective | 11838 | 13792 | 13796 | 15155 | 89 |
╰----------------------------------------+-----------------+-------+--------+-------+---------╯
```bash
╭----------------------------------------+-----------------+---------+---------+---------+---------╮
| src/MathTester.sol:MathTester Contract | | | | | |
+==================================================================================================+
| Deployment Cost | Deployment Size | | | | |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| 2011993 | 9094 | | | | |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| | | | | | |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| Function Name | Min | Avg | Median | Max | # Calls |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| addJacobian | 1800 | 1800 | 1800 | 1800 | 88 |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| addProjective | 1015 | 1015 | 1015 | 1015 | 44 |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| addProjectiveMixed | 967 | 967 | 967 | 967 | 44 |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| doubleJacobian | 794 | 794 | 794 | 794 | 45 |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| doubleProjective | 546 | 546 | 546 | 546 | 45 |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulEcTriplet | 205548 | 1140310 | 1546125 | 1958623 | 43 |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| mulProjectiveTriplet | 6436 | 193298 | 334976 | 385511 | 43 |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| toAffineJacobian | 40275 | 47740 | 47859 | 53863 | 133 |
|----------------------------------------+-----------------+---------+---------+---------+---------|
| toAffineProjective | 11056 | 13787 | 13904 | 16105 | 176 |
╰----------------------------------------+-----------------+---------+---------+---------+---------╯
```
## Contributing

View File

@ -46,6 +46,41 @@ contract MathTester {
return EllipticCurveProjective.projectiveAddMixed(x1, y1, 1, x2, y2);
}
function mulEcTriplet(
uint256 x1,
uint256 y1,
uint256 k1,
uint256 x2,
uint256 y2,
uint256 k2,
uint256 x3,
uint256 y3,
uint256 k3
) public pure returns(uint256, uint256) {
(x1, y1) = EllipticCurve.ecMul(k1, x1, y1, A, P);
(x2, y2) = EllipticCurve.ecMul(k2, x2, y2, A, P);
(x3, y3) = EllipticCurve.ecMul(k3, x3, y3, A, P);
(x1, y1) = EllipticCurve.ecAdd(x1, y1, x2, y2, A, P);
(x1, y1) = EllipticCurve.ecAdd(x1, y1, x3, y3, A, P);
return (x1, y1);
}
function mulProjectiveTriplet(
uint256 x1,
uint256 y1,
uint256 k1,
uint256 x2,
uint256 y2,
uint256 k2,
uint256 x3,
uint256 y3,
uint256 k3
) public pure returns(uint256, uint256, uint256) {
return EllipticCurveProjective.mulAddProjectiveTriplet(x1, y1, k1, x2, y2, k2, x3, y3, k3);
}
function toAffineJacobian(uint256 x, uint256 y, uint256 z) public pure returns (uint256, uint256) {
return EllipticCurve.toAffine(x, y, z, P);
}

View File

@ -2,6 +2,9 @@
pragma solidity ^0.8.0;
// TODO: are there better algorithms than Straus-Shamir trick for
// k1*P1 + k2*P2 + k3*P3
// Vectors for secp256k1 are difficult to find. These are the vectors from:
// https://web.archive.org/web/20190724010836/https://chuckbatson.wordpress.com/2014/11/26/secp256k1-test-vectors
@ -14,6 +17,132 @@ library EllipticCurveProjective {
uint256 private constant B3 = 21;
function findMaxBitLength(
uint256 k1,
uint256 k2,
uint256 k3
) internal pure returns (uint256 bits) {
assembly {
// Find maximum of the three scalars
let max := k1
if gt(k2, max) { max := k2 }
if gt(k3, max) { max := k3 }
// if (v >> 128 != 0) { v >>= 128; bits += 128; }
if gt(max, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
max := shr(128, max)
bits := 128
}
// if (v >> 64 != 0) { v >>= 64; bits += 64; }
if gt(max, 0xFFFFFFFFFFFFFFFF) {
max := shr(64, max)
bits := add(bits, 64)
}
// if (v >> 32 != 0) { v >>= 32; bits += 32; }
if gt(max, 0xFFFFFFFF) {
max := shr(32, max)
bits := add(bits, 32)
}
// if (v >> 16 != 0) { v >>= 16; bits += 16; }
if gt(max, 0xFFFF) {
max := shr(16, max)
bits := add(bits, 16)
}
// if (v >> 8 != 0) { v >>= 8; bits += 8; }
if gt(max, 0xFF) {
max := shr(8, max)
bits := add(bits, 8)
}
// if (v >> 4 != 0) { v >>= 4; bits += 4; }
if gt(max, 0xF) {
max := shr(4, max)
bits := add(bits, 4)
}
// if (v >> 2 != 0) { v >>= 2; bits += 2; }
if gt(max, 0x3) {
max := shr(2, max)
bits := add(bits, 2)
}
// if (v >> 1 != 0) { /* v >>= 1; */ bits += 1; }
if gt(max, 0x1) {
bits := add(bits, 1)
}
bits := add(bits, 1)
}
}
function mulAddProjectiveTriplet(
uint256 x1, uint256 y1, uint256 k1,
uint256 x2, uint256 y2, uint256 k2,
uint256 x3, uint256 y3, uint256 k3
) internal pure returns (uint256 x4, uint256 y4, uint256 z4) {
// We implement the Straus-Shamir trick described in
// Trading Inversions for Multiplications in Elliptic Curve Cryptography.
// (https://eprint.iacr.org/2003/257.pdf Page 7).
uint256 bits = findMaxBitLength(k1, k2, k3);
uint256[8] memory precomputedXs;
uint256[8] memory precomputedYs;
uint256[8] memory precomputedZs;
precomputedXs[1] = x3; precomputedYs[1] = y3; precomputedZs[1] = 1; // 001: P3
precomputedXs[2] = x2; precomputedYs[2] = y2; precomputedZs[2] = 1; // 010: P2
precomputedXs[4] = x1; precomputedYs[4] = y1; precomputedZs[4] = 1; // 100: P1
(precomputedXs[6], precomputedYs[6], precomputedZs[6]) = projectiveAddMixed(x1, y1, 1, x2, y2); // 110: P1+P2
(precomputedXs[5], precomputedYs[5], precomputedZs[5]) = projectiveAddMixed(x1, y1, 1, x3, y3); // 101: P1+P3
(x4, y4, z4) = projectiveAddMixed(x2, y2, 1, x3, y3); // 011: P2+P3
precomputedXs[3] = x4;
precomputedYs[3] = y4;
precomputedZs[3] = z4;
(x4, y4, z4) = projectiveAddMixed(x4, y4, z4, x1, y1); // 111: P1+P2+P3
precomputedXs[7] = x4;
precomputedYs[7] = y4;
precomputedZs[7] = z4;
x4 = 0;
y4 = 1;
z4 = 0;
for (; bits > 0;) {
unchecked { --bits; }
(x4, y4, z4) = projectiveDouble(x4, y4, z4);
uint8 mask;
uint8 bitmask;
assembly {
mask := or(
shl(2, and(shr(bits, k1), 1)),
or(
shl(1, and(shr(bits, k2), 1)),
and(shr(bits, k3), 1)
)
)
// bitmask for 1,2,4 is 22 = 0b00010110
bitmask := and(shl(22, mask), 1)
}
if (mask == 0) {
continue;
}
if (bitmask == 1) {
(x4, y4, z4) = projectiveAddMixed(
x4, y4, z4, precomputedXs[mask], precomputedYs[mask]
);
} else {
(x4, y4, z4) = projectiveAdd(
x4, y4, z4, precomputedXs[mask], precomputedYs[mask], precomputedZs[mask]
);
}
}
}
function projectiveAddMixed(
uint256 X1,
uint256 Y1,

View File

@ -22,7 +22,43 @@ contract MathTesterTest is Test {
points = abi.decode(data, (Point[]));
}
function test_projectiveAddition() public view {
function test_triplet() public view {
uint256 len = points.length - 2;
for (uint256 i; i < len;) {
(uint256 x_p, uint256 y_p, uint256 z_p) = math.mulProjectiveTriplet(
uint256(points[i].x),
uint256(points[i].y),
uint256(points[i].k),
uint256(points[i+1].x),
uint256(points[i+1].y),
uint256(points[i+1].k),
uint256(points[i+2].x),
uint256(points[i+2].y),
uint256(points[i+2].k)
);
(x_p, y_p) = math.toAffineProjective(x_p, y_p, z_p);
(uint256 x_j, uint256 y_j) = math.mulEcTriplet(
uint256(points[i].x),
uint256(points[i].y),
uint256(points[i].k),
uint256(points[i+1].x),
uint256(points[i+1].y),
uint256(points[i+1].k),
uint256(points[i+2].x),
uint256(points[i+2].y),
uint256(points[i+2].k)
);
// (x_j, y_j) = math.toAffineJacobian(x_j, y_j, z_j);
assertEq(x_p, x_j);
assertEq(y_p, y_j);
unchecked { ++i; }
}
}
function test_addition() public view {
uint256 len = points.length - 1;
for (uint256 i; i < len;) {
(uint256 x_p, uint256 y_p, uint256 z_p) = math.addProjective(
@ -48,7 +84,7 @@ contract MathTesterTest is Test {
}
}
function test_projectiveAdditionMixed() public view {
function test_mixedAddition() public view {
uint256 len = points.length - 1;
for (uint256 i; i < len;) {
(uint256 x_p, uint256 y_p, uint256 z_p) = math.addProjectiveMixed(