Skip to content

Commit 920f3b6

Browse files
Add Half() method to scalar + test
1 parent bbef6fc commit 920f3b6

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

Src/Autarkysoft.Bitcoin/Cryptography/EllipticCurve/Scalar8x32.cs

+56
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,62 @@ public bool IsHigh
297297
}
298298
}
299299

300+
301+
/// <summary>
302+
/// Multiply a scalar with the multiplicative inverse of 2
303+
/// </summary>
304+
/// <returns></returns>
305+
public Scalar8x32 Half()
306+
{
307+
// Writing `/` for field division and `//` for integer division, we compute
308+
//
309+
// a/2 = (a - (a&1))/2 + (a&1)/2
310+
// = (a >> 1) + (a&1 ? 1/2 : 0)
311+
// = (a >> 1) + (a&1 ? n//2+1 : 0),
312+
//
313+
// where n is the group order and in the last equality we have used 1/2 = n//2+1 (mod n).
314+
// For n//2, we have the constants SECP256K1_N_H_0, ...
315+
//
316+
// This sum does not overflow. The most extreme case is a = -2, the largest odd scalar.
317+
// Here:
318+
// - the left summand is: a >> 1 = (a - a&1)/2 = (n-2-1)//2 = (n-3)//2
319+
// - the right summand is: a&1 ? n//2+1 : 0 = n//2+1 = (n-1)//2 + 2//2 = (n+1)//2
320+
// Together they sum to (n-3)//2 + (n+1)//2 = (2n-2)//2 = n - 1, which is less than n.
321+
322+
uint mask = (uint)-(b0 & 1U);
323+
ulong t = (b0 >> 1) | (b1 << 31);
324+
Debug.Assert(GetOverflow(this) == 0);
325+
326+
t += (NH0 + 1U) & mask;
327+
uint r0 = (uint)t; t >>= 32;
328+
t += (b1 >> 1) | (b2 << 31);
329+
t += NH1 & mask;
330+
uint r1 = (uint)t; t >>= 32;
331+
t += (b2 >> 1) | (b3 << 31);
332+
t += NH2 & mask;
333+
uint r2 = (uint)t; t >>= 32;
334+
t += (b3 >> 1) | (b4 << 31);
335+
t += NH3 & mask;
336+
uint r3 = (uint)t; t >>= 32;
337+
t += (b4 >> 1) | (b5 << 31);
338+
t += NH4 & mask;
339+
uint r4 = (uint)t; t >>= 32;
340+
t += (b5 >> 1) | (b6 << 31);
341+
t += NH5 & mask;
342+
uint r5 = (uint)t; t >>= 32;
343+
t += (b6 >> 1) | (b7 << 31);
344+
t += NH6 & mask;
345+
uint r6 = (uint)t; t >>= 32;
346+
uint r7 = (uint)t + (b7 >> 1) + (NH7 & mask);
347+
348+
// The line above only computed the bottom 32 bits of r->d[7]. Redo the computation
349+
// in full 64 bits to make sure the top 32 bits are indeed zero.
350+
Debug.Assert((t + (b7 >> 1) + (NH7 & mask)) >> 32 == 0);
351+
352+
return new Scalar8x32(r0, r1, r2, r3, r4, r5, r6, r7);
353+
}
354+
355+
300356
private uint CheckOverflow()
301357
{
302358
uint yes = 0U;

Src/Tests/Bitcoin/Cryptography/EllipticCurve/Scalar8x32Tests.cs

+25
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ public class Scalar8x32Tests
2626
private const ulong NN2 = 0xFFFFFFFFFFFFFFFEUL;
2727
private const ulong NN3 = 0xFFFFFFFFFFFFFFFFUL;
2828

29+
30+
private static Scalar8x32 GetRandom()
31+
{
32+
Random rng = new();
33+
byte[] b32 = new byte[32];
34+
do
35+
{
36+
rng.NextBytes(b32);
37+
Scalar8x32 res = new(b32, out bool overflow);
38+
if (!overflow && !res.IsZero)
39+
{
40+
return res;
41+
}
42+
} while (true);
43+
}
44+
45+
2946
[Fact]
3047
public void Constructor_uintTest()
3148
{
@@ -307,6 +324,14 @@ public void IsZeroTest(uint u0, uint u1, uint u2, uint u3, uint u4, uint u5, uin
307324
Assert.Equal(expected, scalar.IsZero);
308325
}
309326

327+
[Fact]
328+
public void HalfTest()
329+
{
330+
Scalar8x32 s = GetRandom();
331+
Scalar8x32 r = s.Add(s, out _);
332+
r = r.Half();
333+
Assert.True(s.Equals(r));
334+
}
310335

311336
public static IEnumerable<object[]> GetMultCases()
312337
{

0 commit comments

Comments
 (0)