diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbd62be --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/obj +**/bin +.vs/ +packages/ diff --git a/BNSharp.Tests/BNArithmeticTests.cs b/BNSharp.Tests/BNArithmeticTests.cs new file mode 100644 index 0000000..1c4f341 --- /dev/null +++ b/BNSharp.Tests/BNArithmeticTests.cs @@ -0,0 +1,703 @@ +using BNSharp.Tests.Extensions; +using NUnit.Framework; +using System; + +namespace BNSharp.Tests +{ + [TestFixture, Category("BNArithmetic")] + public class BNArithmeticTests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void Add() + { + //should add numbers + Assert.AreEqual(new BN(14).Add(new BN(26)).ToString(16), "28"); + var k1 = new BN(0x1234); + var r1 = k1; + + for (var i = 0; i < 257; i++) + { + r1 = r1.Add(k1); + } + + Assert.AreEqual(r1.ToString(16), "125868"); + + //should handle carry properly (in-place) + var k2 = new BN("abcdefabcdefabcdef", 16); + var r2 = new BN("deadbeef", 16); + + for (var i = 0; i < 257; i++) + { + r2.Iadd(k2); + } + + Assert.AreEqual(r2.ToString(16), "ac79bd9b79be7a277bde"); + + //should properly do positive + negative + var a = new BN("abcd", 16); + var b = new BN("-abce", 16); + + Assert.AreEqual(a.Iadd(b).ToString(16), "-1"); + + a = new BN("abcd", 16); + b = new BN("-abce", 16); + + Assert.AreEqual(a.Add(b).ToString(16), "-1"); + Assert.AreEqual(b.Add(a).ToString(16), "-1"); + + Assert.Pass(); + } + + [Test] + public void Iaddn() + { + //should allow a sign change + var a1 = new BN(-100); + Assert.AreEqual(a1.Negative, 1); + + a1.Iaddn(200); + + Assert.AreEqual(a1.Negative, 0); + Assert.AreEqual(a1.ToString(), "100"); + + //should add negative number + var a2 = new BN(-100); + Assert.AreEqual(a2.Negative, 1); + + a2.Iaddn(-200); + + Assert.AreEqual(a2.ToString(), "-300"); + + //should allow neg + pos with big number + var a3 = new BN("-1000000000", 10); + Assert.AreEqual(a3.Negative, 1); + + a3.Iaddn(200); + + Assert.AreEqual(a3.ToString(), "-999999800"); + + //should carry limb + var a4 = new BN("3ffffff", 16); + + Assert.AreEqual(a4.Iaddn(1).ToString(16), "4000000"); + + //should throw error with num eq 0x4000000 + Assert.Throws(() => { + new BN(0).Iaddn(0x4000000); + }); + + //should reset sign if value equal to value in instance + var a5 = new BN(-1); + Assert.AreEqual(a5.Addn(1).ToString(), "0"); + + Assert.Pass(); + } + + [Test] + public void Sub() + { + //should subtract small numbers + Assert.AreEqual(new BN(26).Sub(new BN(14)).ToString(16), "c"); + Assert.AreEqual(new BN(14).Sub(new BN(26)).ToString(16), "-c"); + Assert.AreEqual(new BN(26).Sub(new BN(26)).ToString(16), "0"); + Assert.AreEqual(new BN(-26).Sub(new BN(26)).ToString(16), "-34"); + + // + var a1 = new BN( + "31ff3c61db2db84b9823d320907a573f6ad37c437abe458b1802cda041d6384" + + "a7d8daef41395491e2", + 16); + var b1 = new BN( + "6f0e4d9f1d6071c183677f601af9305721c91d31b0bbbae8fb790000", + 16); + var r1 = new BN( + "31ff3c61db2db84b9823d3208989726578fd75276287cd9516533a9acfb9a67" + + "76281f34583ddb91e2", + 16); + + //should subtract big numbers + Assert.AreEqual(a1.Sub(b1).Cmp(r1), 0); + + //should subtract numbers in place + Assert.AreEqual(b1.Clone().Isub(a1).Neg().Cmp(r1), 0); + + //should subtract with carry + var a2 = new BN("12345", 16); + var b2 = new BN("1000000000000", 16); + Assert.AreEqual(a2.Isub(b2).ToString(16), "-fffffffedcbb"); + + a2 = new BN("12345", 16); + b2 = new BN("1000000000000", 16); + Assert.AreEqual(b2.Isub(a2).ToString(16), "fffffffedcbb"); + + Assert.Pass(); + } + + [Test] + public void Isubn() + { + //should subtract negative number + var r = new BN( + "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b", 16); + Assert.AreEqual(r.Isubn(-1).ToString(16), + "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681c"); + + //should work for positive numbers + var a1 = new BN(-100); + Assert.AreEqual(a1.Negative, 1); + + a1.Isubn(200); + Assert.AreEqual(a1.Negative, 1); + Assert.AreEqual(a1.ToString(), "-300"); + + //should not allow a sign change + var a2 = new BN(-100); + Assert.AreEqual(a2.Negative, 1); + + a2.Isubn(-200); + Assert.AreEqual(a2.Negative, 0); + Assert.AreEqual(a2.ToString(), "100"); + + //should change sign on small numbers at 0 + var a3 = new BN(0).Subn(2); + Assert.AreEqual(a3.ToString(), "-2"); + + //should change sign on small numbers at 1 + var a4 = new BN(1).Subn(2); + Assert.AreEqual(a4.ToString(), "-1"); + + //should throw error with num eq 0x4000000 + Assert.Throws(() => { + new BN(0).Isubn(0x4000000); + }); + + Assert.Pass(); + } + + private void MulTestMethod(Func mul) + { + //should multiply numbers of different signs + var offsets = new int[] { + 1, // smallMulTo + 250, // comb10MulTo + 1000, // bigMulTo + 15000 // jumboMulTo + }; + + for (var i = 0; i < offsets.Length; ++i) + { + var x = new BN(1).Ishln(offsets[i]); + + Assert.AreEqual(mul(x, x).IsNeg(), false); + Assert.AreEqual(mul(x, x.Neg()).IsNeg(), true); + Assert.AreEqual(mul(x.Neg(), x).IsNeg(), true); + Assert.AreEqual(mul(x.Neg(), x.Neg()).IsNeg(), false); + } + + //should multiply with carry + var n1 = new BN(0x1001); + var r = n1; + + for (var i = 0; i < 4; i++) + { + r = mul(r, n1); + } + + Assert.AreEqual(r.ToString(16), "100500a00a005001"); + + //should correctly multiply big numbers + var n2 = new BN( + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + 16 + ); + Assert.AreEqual( + mul(n2, n2).ToString(16), + "39e58a8055b6fb264b75ec8c646509784204ac15a8c24e05babc9729ab9" + + "b055c3a9458e4ce3289560a38e08ba8175a9446ce14e608245ab3a9" + + "978a8bd8acaa40"); + Assert.AreEqual( + mul(mul(n2, n2), n2).ToString(16), + "1b888e01a06e974017a28a5b4da436169761c9730b7aeedf75fc60f687b" + + "46e0cf2cb11667f795d5569482640fe5f628939467a01a612b02350" + + "0d0161e9730279a7561043af6197798e41b7432458463e64fa81158" + + "907322dc330562697d0d600"); + + //should multiply neg number on 0 + Assert.AreEqual( + mul(new BN("-100000000000"), new BN("3").Div(new BN("4"))) + .ToString(16), + "0" + ); + + //should regress mul big numbers + var q = Fixtures.DhGroups["p17"]["q"]; + var qs = Fixtures.DhGroups["p17"]["qs"]; + + var qBN = new BN(q, 16); + Assert.AreEqual(mul(qBN, qBN).ToString(16), qs); + } + + [Test] + public void Mul() + { + MulTestMethod((x, y) => + { + return x.Mul(y); + }); + + Assert.Pass(); + } + + [Test] + public void Mulf() + { + MulTestMethod((x, y) => + { + return x.Mulf(y); + }); + + Assert.Pass(); + } + + [Test] + public void Imul() + { + //should multiply numbers in-place + var a1 = new BN("abcdef01234567890abcd", 16); + var b1 = new BN("deadbeefa551edebabba8", 16); + var c1 = a1.Mul(b1); + + Assert.AreEqual(a1.Imul(b1).ToString(16), c1.ToString(16)); + + a1 = new BN("abcdef01234567890abcd214a25123f512361e6d236", 16); + b1 = new BN("deadbeefa551edebabba8121234fd21bac0341324dd", 16); + c1 = a1.Mul(b1); + + Assert.AreEqual(a1.Imul(b1).ToString(16), c1.ToString(16)); + + //should multiply by 0 + var a2 = new BN("abcdef01234567890abcd", 16); + var b2 = new BN("0", 16); + var c2 = a2.Mul(b2); + + Assert.AreEqual(a2.Imul(b2).ToString(16), c2.ToString(16)); + + //should regress mul big numbers in-place + var q = Fixtures.DhGroups["p17"]["q"]; + var qs = Fixtures.DhGroups["p17"]["qs"]; + + var qBN = new BN(q, 16); + + Assert.AreEqual(qBN.Isqr().ToString(16), qs); + + Assert.Pass(); + } + + [Test] + public void Muln() + { + //should multiply number by small number + var a1 = new BN("abcdef01234567890abcd", 16); + var b1 = new BN("dead", 16); + var c1 = a1.Mul(b1); + + Assert.AreEqual(a1.Muln(0xdead).ToString(16), c1.ToString(16)); + + //should throw error with num eq 0x4000000 + Assert.Throws(() => { + new BN(0).Imuln(0x4000000); + }); + + //should negate number if number is negative + var a2 = new BN("dead", 16); + Assert.AreEqual(a2.Clone().Imuln(-1).ToString(16), a2.Clone().Neg().ToString(16)); + Assert.AreEqual(a2.Clone().Muln(-1).ToString(16), a2.Clone().Neg().ToString(16)); + + var b2 = new BN("dead", 16); + Assert.AreEqual(b2.Clone().Imuln(-42).ToString(16), b2.Clone().Neg().Muln(42).ToString(16)); + Assert.AreEqual(b2.Clone().Muln(-42).ToString(16), b2.Clone().Neg().Muln(42).ToString(16)); + + Assert.Pass(); + } + + [Test] + public void Pow() + { + //should raise number to the power + var a = new BN("ab", 16); + var b = new BN("13", 10); + var c = a.Pow(b); + + Assert.AreEqual(c.ToString(16), "15963da06977df51909c9ba5b"); + + Assert.Pass(); + } + + [Test] + public void Div() + { + //should divide small numbers (<=26 bits) + Assert.AreEqual(new BN("256").Div(new BN(10)).ToString(10), + "25"); + Assert.AreEqual(new BN("-256").Div(new BN(10)).ToString(10), + "-25"); + Assert.AreEqual(new BN("256").Div(new BN(-10)).ToString(10), + "-25"); + Assert.AreEqual(new BN("-256").Div(new BN(-10)).ToString(10), + "25"); + + Assert.AreEqual(new BN("10").Div(new BN(256)).ToString(10), + "0"); + Assert.AreEqual(new BN("-10").Div(new BN(256)).ToString(10), + "0"); + Assert.AreEqual(new BN("10").Div(new BN(-256)).ToString(10), + "0"); + Assert.AreEqual(new BN("-10").Div(new BN(-256)).ToString(10), + "0"); + + //should divide large numbers (>53 bits) + Assert.AreEqual(new BN("1222222225255589").Div(new BN("611111124969028")) + .ToString(10), "1"); + Assert.AreEqual(new BN("-1222222225255589").Div(new BN("611111124969028")) + .ToString(10), "-1"); + Assert.AreEqual(new BN("1222222225255589").Div(new BN("-611111124969028")) + .ToString(10), "-1"); + Assert.AreEqual(new BN("-1222222225255589").Div(new BN("-611111124969028")) + .ToString(10), "1"); + + Assert.AreEqual(new BN("611111124969028").Div(new BN("1222222225255589")) + .ToString(10), "0"); + Assert.AreEqual(new BN("-611111124969028").Div(new BN("1222222225255589")) + .ToString(10), "0"); + Assert.AreEqual(new BN("611111124969028").Div(new BN("-1222222225255589")) + .ToString(10), "0"); + Assert.AreEqual(new BN("-611111124969028").Div(new BN("-1222222225255589")) + .ToString(10), "0"); + + //should divide numbers + Assert.AreEqual(new BN("69527932928").Div(new BN("16974594")).ToString(16), + "fff"); + Assert.AreEqual(new BN("-69527932928").Div(new BN("16974594")).ToString(16), + "-fff"); + + var b = new BN( + "39e58a8055b6fb264b75ec8c646509784204ac15a8c24e05babc9729ab9" + + "b055c3a9458e4ce3289560a38e08ba8175a9446ce14e608245ab3a9" + + "978a8bd8acaa40", + 16); + var n = new BN( + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + 16 + ); + Assert.AreEqual(b.Div(n).ToString(16), n.ToString(16)); + + Assert.AreEqual(new BN("1").Div(new BN("-5")).ToString(10), "0"); + + //should not fail on regression after moving to _wordDiv + // Regression after moving to word div + var p = new BN( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + 16); + var a = new BN( + "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + 16); + var @as = a.Sqr(); + Assert.AreEqual( + @as.Div(p).ToString(16), + "39e58a8055b6fb264b75ec8c646509784204ac15a8c24e05babc9729e58090b9"); + + p = new BN( + "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + 16); + a = new BN( + "fffffffe00000003fffffffd0000000200000001fffffffe00000002ffffffff" + + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + 16); + Assert.AreEqual( + a.Div(p).ToString(16), + "ffffffff00000002000000000000000000000001000000000000000000000001"); + + Assert.Pass(); + } + + [Test] + public void Idivn() + { + //should divide numbers in-place + Assert.AreEqual(new BN("10", 16).Idivn(3).ToString(16), "5"); + Assert.AreEqual(new BN("10", 16).Idivn(-3).ToString(16), "-5"); + Assert.AreEqual(new BN("12", 16).Idivn(3).ToString(16), "6"); + Assert.AreEqual(new BN("10000000000000000").Idivn(3).ToString(10), + "3333333333333333"); + Assert.AreEqual( + new BN("100000000000000000000000000000").Idivn(3).ToString(10), + "33333333333333333333333333333"); + + var t = new BN(3); + Assert.AreEqual( + new BN("12345678901234567890123456", 16).Idivn(3).ToString(16), + new BN("12345678901234567890123456", 16).Div(t).ToString(16)); + + Assert.Pass(); + } + + [Test] + public void DivRound() + { + //should divide numbers with rounding + Assert.AreEqual(new BN(9).DivRound(new BN(20)).ToString(10), + "0"); + Assert.AreEqual(new BN(10).DivRound(new BN(20)).ToString(10), + "1"); + Assert.AreEqual(new BN(150).DivRound(new BN(20)).ToString(10), + "8"); + Assert.AreEqual(new BN(149).DivRound(new BN(20)).ToString(10), + "7"); + Assert.AreEqual(new BN(149).DivRound(new BN(17)).ToString(10), + "9"); + Assert.AreEqual(new BN(144).DivRound(new BN(17)).ToString(10), + "8"); + Assert.AreEqual(new BN(-144).DivRound(new BN(17)).ToString(10), + "-8"); + + //should return 1 on exact division + Assert.AreEqual(new BN(144).DivRound(new BN(144)).ToString(10), "1"); + + Assert.Pass(); + } + + [Test] + public void Mod() + { + //should modulo small numbers (<=26 bits) + Assert.AreEqual(new BN("256").Mod(new BN(10)).ToString(10), + "6"); + Assert.AreEqual(new BN("-256").Mod(new BN(10)).ToString(10), + "-6"); + Assert.AreEqual(new BN("256").Mod(new BN(-10)).ToString(10), + "6"); + Assert.AreEqual(new BN("-256").Mod(new BN(-10)).ToString(10), + "-6"); + + Assert.AreEqual(new BN("10").Mod(new BN(256)).ToString(10), + "10"); + Assert.AreEqual(new BN("-10").Mod(new BN(256)).ToString(10), + "-10"); + Assert.AreEqual(new BN("10").Mod(new BN(-256)).ToString(10), + "10"); + Assert.AreEqual(new BN("-10").Mod(new BN(-256)).ToString(10), + "-10"); + + //should modulo large numbers (>53 bits) + Assert.AreEqual(new BN("1222222225255589").Mod(new BN("611111124969028")) + .ToString(10), "611111100286561"); + Assert.AreEqual(new BN("-1222222225255589").Mod(new BN("611111124969028")) + .ToString(10), "-611111100286561"); + Assert.AreEqual(new BN("1222222225255589").Mod(new BN("-611111124969028")) + .ToString(10), "611111100286561"); + Assert.AreEqual(new BN("-1222222225255589").Mod(new BN("-611111124969028")) + .ToString(10), "-611111100286561"); + + Assert.AreEqual(new BN("611111124969028").Mod(new BN("1222222225255589")) + .ToString(10), "611111124969028"); + Assert.AreEqual(new BN("-611111124969028").Mod(new BN("1222222225255589")) + .ToString(10), "-611111124969028"); + Assert.AreEqual(new BN("611111124969028").Mod(new BN("-1222222225255589")) + .ToString(10), "611111124969028"); + Assert.AreEqual(new BN("-611111124969028").Mod(new BN("-1222222225255589")) + .ToString(10), "-611111124969028"); + + //should mod numbers + Assert.AreEqual(new BN("10").Mod(new BN(256)).ToString(16), + "a"); + Assert.AreEqual(new BN("69527932928").Mod(new BN("16974594")).ToString(16), + "102f302"); + + // 178 = 10 * 17 + 8 + Assert.AreEqual(new BN(178).Div(new BN(10)).ToNumber(), 17); + Assert.AreEqual(new BN(178).Mod(new BN(10)).ToNumber(), 8); + Assert.AreEqual(new BN(178).Umod(new BN(10)).ToNumber(), 8); + + // -178 = 10 * (-17) + (-8) + Assert.AreEqual(new BN(-178).Div(new BN(10)).ToNumber(), -17); + Assert.AreEqual(new BN(-178).Mod(new BN(10)).ToNumber(), -8); + Assert.AreEqual(new BN(-178).Umod(new BN(10)).ToNumber(), 2); + + // 178 = -10 * (-17) + 8 + Assert.AreEqual(new BN(178).Div(new BN(-10)).ToNumber(), -17); + Assert.AreEqual(new BN(178).Mod(new BN(-10)).ToNumber(), 8); + Assert.AreEqual(new BN(178).Umod(new BN(-10)).ToNumber(), 8); + + // -178 = -10 * (17) + (-8) + Assert.AreEqual(new BN(-178).Div(new BN(-10)).ToNumber(), 17); + Assert.AreEqual(new BN(-178).Mod(new BN(-10)).ToNumber(), -8); + Assert.AreEqual(new BN(-178).Umod(new BN(-10)).ToNumber(), 2); + + // -4 = 1 * (-3) + -1 + Assert.AreEqual(new BN(-4).Div(new BN(-3)).ToNumber(), 1); + Assert.AreEqual(new BN(-4).Mod(new BN(-3)).ToNumber(), -1); + + // -4 = -1 * (3) + -1 + Assert.AreEqual(new BN(-4).Mod(new BN(3)).ToNumber(), -1); + // -4 = 1 * (-3) + (-1 + 3) + Assert.AreEqual(new BN(-4).Umod(new BN(-3)).ToNumber(), 2); + + var p = new BN( + "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + 16); + var a1 = new BN( + "fffffffe00000003fffffffd0000000200000001fffffffe00000002ffffffff" + + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + 16); + Assert.AreEqual( + a1.Mod(p).ToString(16), + "0"); + + //should properly carry the sign inside division + var a2 = new BN("945304eb96065b2a98b57a48a06ae28d285a71b5", "hex"); + var b = new BN( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + "hex"); + + Assert.AreEqual(a2.Mul(b).Mod(a2).Cmpn(0), 0); + + Assert.Pass(); + } + + [Test] + public void Modrn() + { + //should act like .mod() on small numbers + Assert.AreEqual(new BN("10", 16).Modrn(256).ToString(16), "10"); + Assert.AreEqual(new BN("10", 16).Modrn(-256).ToString(16), "-10"); + Assert.AreEqual(new BN("100", 16).Modrn(256).ToString(16), "0"); + Assert.AreEqual(new BN("1001", 16).Modrn(256).ToString(16), "1"); + Assert.AreEqual(new BN("100000000001", 16).Modrn(256).ToString(16), "1"); + Assert.AreEqual(new BN("100000000001", 16).Modrn(257).ToString(16), + new BN("100000000001", 16).Mod(new BN(257)).ToString(16)); + Assert.AreEqual(new BN("123456789012", 16).Modrn(3).ToString(16), + new BN("123456789012", 16).Mod(new BN(3)).ToString(16)); + + Assert.Pass(); + } + + [Test] + public void Abs() + { + //should return absolute value + Assert.AreEqual(new BN(0x1001).Abs().ToString(), "4097"); + Assert.AreEqual(new BN(-0x1001).Abs().ToString(), "4097"); + Assert.AreEqual(new BN("ffffffff", 16).Abs().ToString(), "4294967295"); + + Assert.Pass(); + } + + [Test] + public void Invm() + { + //should invert relatively-prime numbers + var p = new BN(257); + var a = new BN(3); + var b = a.Invm(p); + Assert.AreEqual(a.Mul(b).Mod(p).ToString(16), "1"); + + var p192 = new BN( + "fffffffffffffffffffffffffffffffeffffffffffffffff", + 16); + a = new BN("deadbeef", 16); + b = a.Invm(p192); + Assert.AreEqual(a.Mul(b).Mod(p192).ToString(16), "1"); + + // Even base + var phi = new BN("872d9b030ba368706b68932cf07a0e0c", 16); + var e = new BN(65537); + var d = e.Invm(phi); + Assert.AreEqual(e.Mul(d).Mod(phi).ToString(16), "1"); + + // Even base (take #2) + a = new BN("5"); + b = new BN("6"); + var r = a.Invm(b); + Assert.AreEqual(r.Mul(a).Mod(b).ToString(16), "1"); + + Assert.Pass(); + } + + [Test] + public void Gcd() + { + //should return GCD + Assert.AreEqual(new BN(3).Gcd(new BN(2)).ToString(10), "1"); + Assert.AreEqual(new BN(18).Gcd(new BN(12)).ToString(10), "6"); + Assert.AreEqual(new BN(-18).Gcd(new BN(12)).ToString(10), "6"); + Assert.AreEqual(new BN(-18).Gcd(new BN(-12)).ToString(10), "6"); + Assert.AreEqual(new BN(-18).Gcd(new BN(0)).ToString(10), "18"); + Assert.AreEqual(new BN(0).Gcd(new BN(-18)).ToString(10), "18"); + Assert.AreEqual(new BN(2).Gcd(new BN(0)).ToString(10), "2"); + Assert.AreEqual(new BN(0).Gcd(new BN(3)).ToString(10), "3"); + Assert.AreEqual(new BN(0).Gcd(new BN(0)).ToString(10), "0"); + + Assert.Pass(); + } + + [Test] + public void Egcd() + { + //should return EGCD + Assert.AreEqual(new BN(3).Egcd(new BN(2)).Gcd.ToString(10), "1"); + Assert.AreEqual(new BN(18).Egcd(new BN(12)).Gcd.ToString(10), "6"); + Assert.AreEqual(new BN(-18).Egcd(new BN(12)).Gcd.ToString(10), "6"); + Assert.AreEqual(new BN(0).Egcd(new BN(12)).Gcd.ToString(10), "12"); + + //should not allow 0 input + Assert.Throws(() => { + new BN(1).Egcd(new BN(0)); + }); + + //should not allow negative input + Assert.Throws(() => { + new BN(1).Egcd(new BN(-1)); + }); + + Assert.Pass(); + } + + [Test] + public void Max() + { + //should return maximum + Assert.AreEqual(BN.Max(new BN(3), new BN(2)).ToString(16), "3"); + Assert.AreEqual(BN.Max(new BN(2), new BN(3)).ToString(16), "3"); + Assert.AreEqual(BN.Max(new BN(2), new BN(2)).ToString(16), "2"); + Assert.AreEqual(BN.Max(new BN(2), new BN(-2)).ToString(16), "2"); + + Assert.Pass(); + } + + [Test] + public void Min() + { + //should return minimum + Assert.AreEqual(BN.Min(new BN(3), new BN(2)).ToString(16), "2"); + Assert.AreEqual(BN.Min(new BN(2), new BN(3)).ToString(16), "2"); + Assert.AreEqual(BN.Min(new BN(2), new BN(2)).ToString(16), "2"); + Assert.AreEqual(BN.Min(new BN(2), new BN(-2)).ToString(16), "-2"); + + Assert.Pass(); + } + + [Test] + public void Ineg() + { + //shouldn\"t change sign for zero + Assert.AreEqual(new BN(0).Ineg().ToString(10), "0"); + + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/BNSharp.Tests/BNBinaryTests.cs b/BNSharp.Tests/BNBinaryTests.cs new file mode 100644 index 0000000..691d625 --- /dev/null +++ b/BNSharp.Tests/BNBinaryTests.cs @@ -0,0 +1,279 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; + +namespace BNSharp.Tests +{ + [TestFixture, Category("BNBinary")] + public class BNBinaryTests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void Shl() + { + //should shl numbers + // TODO(indutny): add negative numbers when the time will come + Assert.AreEqual(new BN("69527932928").Shln(13).ToString(16), + "2060602000000"); + Assert.AreEqual(new BN("69527932928").Shln(45).ToString(16), + "206060200000000000000"); + + //should ushl numbers + Assert.AreEqual(new BN("69527932928").Ushln(13).ToString(16), + "2060602000000"); + Assert.AreEqual(new BN("69527932928").Ushln(45).ToString(16), + "206060200000000000000"); + + Assert.Pass(); + } + + [Test] + public void Shr() + { + //should shr numbers + // TODO(indutny): add negative numbers when the time will come + Assert.AreEqual(new BN("69527932928").Shrn(13).ToString(16), + "818180"); + Assert.AreEqual(new BN("69527932928").Shrn(17).ToString(16), + "81818"); + Assert.AreEqual(new BN("69527932928").Shrn(256).ToString(16), + "0"); + + //should ushr numbers + Assert.AreEqual(new BN("69527932928").Ushrn(13).ToString(16), + "818180"); + Assert.AreEqual(new BN("69527932928").Ushrn(17).ToString(16), + "81818"); + Assert.AreEqual(new BN("69527932928").Ushrn(256).ToString(16), + "0"); + + Assert.Pass(); + } + + [Test] + public void Bincn() + { + //should increment bit + Assert.AreEqual(new BN(0).Bincn(1).ToString(16), "2"); + Assert.AreEqual(new BN(2).Bincn(1).ToString(16), "4"); + Assert.AreEqual(new BN(2).Bincn(1).Bincn(1).ToString(16), + new BN(2).Bincn(2).ToString(16)); + Assert.AreEqual(new BN(0xffffff).Bincn(1).ToString(16), "1000001"); + Assert.AreEqual(new BN(2).Bincn(63).ToString(16), + "8000000000000002"); + + Assert.Pass(); + } + + [Test] + public void Imaskn() + { + //should mask bits in-place + Assert.AreEqual(new BN(0).Imaskn(1).ToString(16), "0"); + Assert.AreEqual(new BN(3).Imaskn(1).ToString(16), "1"); + Assert.AreEqual(new BN("123456789", 16).Imaskn(4).ToString(16), "9"); + Assert.AreEqual(new BN("123456789", 16).Imaskn(16).ToString(16), "6789"); + Assert.AreEqual(new BN("123456789", 16).Imaskn(28).ToString(16), "3456789"); + + //should not mask when number is bigger than length + Assert.AreEqual(new BN(0xe3).Imaskn(56).ToString(16), "e3"); + Assert.AreEqual(new BN(0xe3).Imaskn(26).ToString(16), "e3"); + + Assert.Pass(); + } + + [Test] + public void Testn() + { + //should support test specific bit + var list = new List + { + "ff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }; + list.ForEach((hex) => + { + var bn = new BN(hex, 16); + var bl = bn.BitLength(); + + for (var i = 0; i < bl; ++i) + { + Assert.AreEqual(bn.Testn(i), true); + } + + // test off the end + Assert.AreEqual(bn.Testn(bl), false); + }); + + var xbits = "01111001010111001001000100011101" + + "11010011101100011000111001011101" + + "10010100111000000001011000111101" + + "01011111001111100100011110000010" + + "01011010100111010001010011000100" + + "01101001011110100001001111100110" + + "001110010111"; + + var x1 = new BN( + "23478905234580795234378912401239784125643978256123048348957342" + ); + for (var i = 0; i < x1.BitLength(); ++i) + { + Assert.AreEqual(x1.Testn(i), (xbits.ElementAt(i) == '1'), "Failed @ bit " + i); + } + + //should have short-cuts + var x = new BN("abcd", 16); + Assert.IsTrue(!x.Testn(128)); + + Assert.Pass(); + } + + [Test] + public void And() + { + //should and numbers + Assert.AreEqual(new BN("1010101010101010101010101010101010101010", 2) + .And(new BN("101010101010101010101010101010101010101", 2)) + .ToString(2), "0"); + + //should and numbers of different limb-length + Assert.AreEqual( + new BN("abcd0000ffff", 16) + .And(new BN("abcd", 16)).ToString(16), + "abcd"); + + Assert.Pass(); + } + + [Test] + public void Iand() + { + //should iand numbers + Assert.AreEqual(new BN("1010101010101010101010101010101010101010", 2) + .Iand(new BN("101010101010101010101010101010101010101", 2)) + .ToString(2), "0"); + Assert.AreEqual(new BN("1000000000000000000000000000000000000001", 2) + .Iand(new BN("1", 2)) + .ToString(2), "1"); + Assert.AreEqual(new BN("1", 2) + .Iand(new BN("1000000000000000000000000000000000000001", 2)) + .ToString(2), "1"); + + Assert.Pass(); + } + + [Test] + public void Or() + { + //should or numbers + Assert.AreEqual(new BN("1010101010101010101010101010101010101010", 2) + .Or(new BN("101010101010101010101010101010101010101", 2)) + .ToString(2), "1111111111111111111111111111111111111111"); + + //should or numbers of different limb-length + Assert.AreEqual( + new BN("abcd00000000", 16) + .Or(new BN("abcd", 16)).ToString(16), + "abcd0000abcd"); + + Assert.Pass(); + } + + [Test] + public void Ior() + { + //should ior numbers + Assert.AreEqual(new BN("1010101010101010101010101010101010101010", 2) + .Ior(new BN("101010101010101010101010101010101010101", 2)) + .ToString(2), "1111111111111111111111111111111111111111"); + Assert.AreEqual(new BN("1000000000000000000000000000000000000000", 2) + .Ior(new BN("1", 2)) + .ToString(2), "1000000000000000000000000000000000000001"); + Assert.AreEqual(new BN("1", 2) + .Ior(new BN("1000000000000000000000000000000000000000", 2)) + .ToString(2), "1000000000000000000000000000000000000001"); + + Assert.Pass(); + } + + [Test] + public void Xor() + { + //should xor numbers + Assert.AreEqual(new BN("11001100110011001100110011001100", 2) + .Xor(new BN("1100110011001100110011001100110", 2)) + .ToString(2), "10101010101010101010101010101010"); + + Assert.Pass(); + } + + [Test] + public void Ixor() + { + //should ixor numbers + Assert.AreEqual(new BN("11001100110011001100110011001100", 2) + .Ixor(new BN("1100110011001100110011001100110", 2)) + .ToString(2), "10101010101010101010101010101010"); + Assert.AreEqual(new BN("11001100110011001100110011001100", 2) + .Ixor(new BN("1", 2)) + .ToString(2), "11001100110011001100110011001101"); + Assert.AreEqual(new BN("1", 2) + .Ixor(new BN("11001100110011001100110011001100", 2)) + .ToString(2), "11001100110011001100110011001101"); + + //should and numbers of different limb-length + Assert.AreEqual( + new BN("abcd0000ffff", 16) + .Xor(new BN("abcd", 16)).ToString(16), + "abcd00005432"); + + Assert.Pass(); + } + + [Test] + public void Setn() + { + //should allow single bits to be set + Assert.AreEqual(new BN(0).Setn(2, 1).ToString(2), "100"); + Assert.AreEqual(new BN(0).Setn(27, 1).ToString(2), + "1000000000000000000000000000"); + Assert.AreEqual(new BN(0).Setn(63, 1).ToString(16), + new BN(1).Iushln(63).ToString(16)); + Assert.AreEqual(new BN("1000000000000000000000000001", 2).Setn(27, 0) + .ToString(2), "1"); + Assert.AreEqual(new BN("101", 2).Setn(2, 0).ToString(2), "1"); + + Assert.Pass(); + } + + [Test] + public void Notn() + { + //should allow bitwise negation + Assert.AreEqual(new BN("111000111", 2).Notn(9).ToString(2), + "111000"); + Assert.AreEqual(new BN("000111000", 2).Notn(9).ToString(2), + "111000111"); + Assert.AreEqual(new BN("111000111", 2).Notn(9).ToString(2), + "111000"); + Assert.AreEqual(new BN("000111000", 2).Notn(9).ToString(2), + "111000111"); + Assert.AreEqual(new BN("111000111", 2).Notn(32).ToString(2), + "11111111111111111111111000111000"); + Assert.AreEqual(new BN("000111000", 2).Notn(32).ToString(2), + "11111111111111111111111111000111"); + Assert.AreEqual(new BN("111000111", 2).Notn(68).ToString(2), + "11111111111111111111111111111111" + + "111111111111111111111111111000111000"); + Assert.AreEqual(new BN("000111000", 2).Notn(68).ToString(2), + "11111111111111111111111111111111" + + "111111111111111111111111111111000111"); + + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/BNSharp.Tests/BNConstructorTests.cs b/BNSharp.Tests/BNConstructorTests.cs new file mode 100644 index 0000000..c6479d6 --- /dev/null +++ b/BNSharp.Tests/BNConstructorTests.cs @@ -0,0 +1,179 @@ +using BNSharp.Tests.Extensions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace BNSharp.Tests +{ + [TestFixture, Category("BNConstructor")] + public class BNConstructorTests + { + [SetUp] + public void Setup() + { + } + + [Test] + public void WithSmiInput() + { + //should accept one limb number + Assert.AreEqual(new BN(12345).ToString(16), "3039"); + + //should accept two-limb number + Assert.AreEqual(new BN(0x4123456).ToString(16), "4123456"); + + //should accept 52 bits of precision + var num1 = (long)Math.Pow(2, 52); + Assert.AreEqual(new BN(num1, 10).ToString(10), num1.ToString(10)); + + //should accept max safe integer + var num2 = (long)Math.Pow(2, 53) - 1; + Assert.AreEqual(new BN(num2, 10).ToString(10), num2.ToString(10)); + + //should not accept an unsafe integer + var num3 = (long)Math.Pow(2, 53); + + Assert.Throws(() => { + new BN(num3, 10); + }); + + //should accept two-limb LE number + Assert.AreEqual(new BN(0x4123456, null, Endian.LittleEndian).ToString(16), "56341204"); + + Assert.Pass(); + } + + [Test] + public void WithStringInput() + { + //should accept base-16 + Assert.AreEqual(new BN("1A6B765D8CDF", 16).ToString(16), "1a6b765d8cdf"); + Assert.AreEqual(new BN("1A6B765D8CDF", 16).ToString(), "29048849665247"); + + //should accept base-hex + Assert.AreEqual(new BN("FF", "hex").ToString(), "255"); + + //should accept base-16 with spaces + var num1 = "a89c e5af8724 c0a23e0e 0ff77500"; + Assert.AreEqual(new BN(num1, 16).ToString(16), Regex.Replace(num1, " ", "")); + + //should accept long base-16 + var num2 = "123456789abcdef123456789abcdef123456789abcdef"; + Assert.AreEqual(new BN(num2, 16).ToString(16), num2); + + //should accept positive base-10 + Assert.AreEqual(new BN("10654321").ToString(), "10654321"); + Assert.AreEqual(new BN("29048849665247").ToString(16), "1a6b765d8cdf"); + + //should accept negative base-10 + Assert.AreEqual(new BN("-29048849665247").ToString(16), "-1a6b765d8cdf"); + + //should accept long base-10 + var num3 = "10000000000000000"; + Assert.AreEqual(new BN(num3).ToString(10), num3); + + //should accept base-2 + var base2 = "11111111111111111111111111111111111111111111111111111"; + Assert.AreEqual(new BN(base2, 2).ToString(2), base2); + + //should accept base-36 + var base36 = "zzZzzzZzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"; + Assert.AreEqual(new BN(base36, 36).ToString(36), base36.ToLowerInvariant()); + + //should not overflow limbs during base-10 + var num4 = "65820182292848241686198767302293" + + "20890292528855852623664389292032"; + Assert.IsTrue(new BN(num4).words[0] < 0x4000000); + + //should accept base-16 LE integer + Assert.AreEqual(new BN("1A6B765D8CDF", 16, Endian.LittleEndian).ToString(16), + "df8c5d766b1a"); + + //should accept base-16 LE integer with leading zeros + Assert.AreEqual(new BN("0010", 16, Endian.LittleEndian).ToNumber(), 4096); + Assert.AreEqual(new BN("-010", 16, Endian.LittleEndian).ToNumber(), -4096); + Assert.AreEqual(new BN("010", 16, Endian.LittleEndian).ToNumber(), 4096); + + //should not accept wrong characters for base + Assert.Throws(() => { + new BN("01FF"); + }); + + //should not accept decimal + Assert.Throws(() => { + new BN("10.00", 10); // eslint-disable-line no-new + }); + + Assert.Throws(() => { + new BN("16.00", 16); // eslint-disable-line no-new + }); + + //should not accept non-hex characters + var list = new List + { + "0000000z", + "000000gg", + "0000gg00", + "fffggfff", + "/0000000", + "0-000000", // if -, is first, that is OK + "ff.fffff", + "hexadecimal" + }; + list.ForEach((str) => + { + Assert.Throws(() => { + new BN(str, 16); // eslint-disable-line no-new + }, "Invalid character in"); + }); + + Assert.Pass(); + } + + [Test] + public void WithArrayInput() + { + //should not fail on empty array + Assert.AreEqual(new BN(new byte[0]).ToString(16), "0"); + + //should import/export big endian + Assert.AreEqual(new BN(new byte[] { 0, 1 }, 16).ToString(16), "1"); + Assert.AreEqual(new BN(new byte[] { 1, 2, 3 }).ToString(16), "10203"); + Assert.AreEqual(new BN(new byte[] { 1, 2, 3, 4 }).ToString(16), "1020304"); + Assert.AreEqual(new BN(new byte[] { 1, 2, 3, 4, 5 }).ToString(16), "102030405"); + Assert.AreEqual(new BN(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }).ToString(16), + "102030405060708"); + Assert.AreEqual(string.Join(",", new BN(new byte[] { 1, 2, 3, 4 }).ToArray()), "1,2,3,4"); + Assert.AreEqual(string.Join(",", new BN(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }).ToArray()), + "1,2,3,4,5,6,7,8"); + + //should import little endian + Assert.AreEqual(new BN(new byte[] { 0, 1 }, 16, Endian.LittleEndian).ToString(16), "100"); + Assert.AreEqual(new BN(new byte[] { 1, 2, 3 }, 16, Endian.LittleEndian).ToString(16), "30201"); + Assert.AreEqual(new BN(new byte[] { 1, 2, 3, 4 }, 16, Endian.LittleEndian).ToString(16), "4030201"); + Assert.AreEqual(new BN(new byte[] { 1, 2, 3, 4, 5 }, 16, Endian.LittleEndian).ToString(16), + "504030201"); + Assert.AreEqual(new BN(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }, Endian.LittleEndian).ToString(16), + "807060504030201"); + Assert.AreEqual(string.Join(",", new BN(new byte[] { 1, 2, 3, 4 }).ToArray(Endian.LittleEndian)), "4,3,2,1"); + Assert.AreEqual(string.Join(",", new BN(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }).ToArray(Endian.LittleEndian)), + "8,7,6,5,4,3,2,1"); + + //should import big endian with implicit base + Assert.AreEqual(new BN(new byte[] { 1, 2, 3, 4, 5 }, Endian.LittleEndian).ToString(16), "504030201"); + + Assert.Pass(); + } + + [Test] + public void WithBNInput() + { + //should clone BN + var num = new BN(12345); + Assert.AreEqual(new BN(num).ToString(10), "12345"); + + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/BNSharp.Tests/BNDHTests.cs b/BNSharp.Tests/BNDHTests.cs new file mode 100644 index 0000000..a1af9e7 --- /dev/null +++ b/BNSharp.Tests/BNDHTests.cs @@ -0,0 +1,38 @@ +using NUnit.Framework; +using System; + +namespace BNSharp.Tests +{ + [TestFixture, Category("BNDH")] + public class BNDHTests + { + [SetUp] + public void Setup() + { + } + + private static string ByteArrayToString(byte[] ba) + { + return BitConverter.ToString(ba).Replace("-", "").ToLowerInvariant(); + } + + [Test] + public void DHTest() + { + var groups = Fixtures.DhGroups; + foreach (var groupKVP in groups) { + var name = groupKVP.Key; + var group = groups[name]; + + var @base = new BN(2); + var mont = BN.Red(new BN(group["prime"], 16)); + var priv = new BN(group["priv"], 16); + var multed = @base.ToRed(mont).RedPow(priv).FromRed(); + var actual = ByteArrayToString(multed.ToArray()); + Assert.AreEqual(actual, group["pub"]); + }; + + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/BNSharp.Tests/BNRedTests.cs b/BNSharp.Tests/BNRedTests.cs new file mode 100644 index 0000000..f6673de --- /dev/null +++ b/BNSharp.Tests/BNRedTests.cs @@ -0,0 +1,321 @@ +using BNSharp.Tests.Extensions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BNSharp.Tests +{ + [TestFixture, Category("BNRed")] + public class BNRedTests + { + [SetUp] + public void Setup() + { + } + + private void RedTestMethod(Func fn) + { + //should support add, iadd, sub, isub operations + var p1 = new BN(257); + var m1 = fn(p1); + var a1 = new BN(123).ToRed(m1); + var b1 = new BN(231).ToRed(m1); + + Assert.AreEqual(a1.RedAdd(b1).FromRed().ToString(10), "97"); + Assert.AreEqual(a1.RedSub(b1).FromRed().ToString(10), "149"); + Assert.AreEqual(b1.RedSub(a1).FromRed().ToString(10), "108"); + + Assert.AreEqual(a1.Clone().RedIAdd(b1).FromRed().ToString(10), "97"); + Assert.AreEqual(a1.Clone().RedISub(b1).FromRed().ToString(10), "149"); + Assert.AreEqual(b1.Clone().RedISub(a1).FromRed().ToString(10), "108"); + + //should support pow and mul operations + var p192 = new BN( + "fffffffffffffffffffffffffffffffeffffffffffffffff", + 16); + var m2 = fn(p192); + var a2 = new BN(123); + var b2 = new BN(231); + var c2 = a2.ToRed(m2).RedMul(b2.ToRed(m2)).FromRed(); + Assert.IsTrue(c2.Cmp(a2.Mul(b2).Mod(p192)) == 0); + + Assert.AreEqual(a2.ToRed(m2).RedPow(new BN(0)).FromRed() + .Cmp(new BN(1)), 0); + Assert.AreEqual(a2.ToRed(m2).RedPow(new BN(3)).FromRed() + .Cmp(a2.Sqr().Mul(a2)), 0); + Assert.AreEqual(a2.ToRed(m2).RedPow(new BN(4)).FromRed() + .Cmp(a2.Sqr().Sqr()), 0); + Assert.AreEqual(a2.ToRed(m2).RedPow(new BN(8)).FromRed() + .Cmp(a2.Sqr().Sqr().Sqr()), 0); + Assert.AreEqual(a2.ToRed(m2).RedPow(new BN(9)).FromRed() + .Cmp(a2.Sqr().Sqr().Sqr().Mul(a2)), 0); + Assert.AreEqual(a2.ToRed(m2).RedPow(new BN(17)).FromRed() + .Cmp(a2.Sqr().Sqr().Sqr().Sqr().Mul(a2)), 0); + Assert.AreEqual( + a2.ToRed(m2).RedPow(new BN("deadbeefabbadead", 16)).FromRed() + .ToString(16), + "3aa0e7e304e320b68ef61592bcb00341866d6fa66e11a4d6"); + + //should sqrtm numbers + var p3 = new BN(263); + var m3 = fn(p3); + var q3 = new BN(11).ToRed(m3); + + var qr = q3.RedSqrt(); + Assert.AreEqual(qr.RedSqr().Cmp(q3), 0); + + qr = q3.RedSqrt(); + Assert.AreEqual(qr.RedSqr().Cmp(q3), 0); + + //? + /*p3 = new BN( + "fffffffffffffffffffffffffffffffeffffffffffffffff", + 16); + m3 = fn(p3); + + q3 = new BN(13).toRed(m3); + qr = q3.redSqrt(true, p3); + Assert.AreEqual(qr.redSqr().cmp(q3), 0); + + qr = q3.redSqrt(false, p3); + Assert.AreEqual(qr.redSqr().cmp(q3), 0);*/ + + // Tonelli-shanks + p3 = new BN(13); + m3 = fn(p3); + q3 = new BN(10).ToRed(m3); + Assert.AreEqual(q3.RedSqrt().FromRed().ToString(10), "7"); + + //should invm numbers + var p4 = new BN(257); + var m4 = fn(p4); + var a4 = new BN(3).ToRed(m4); + var b4 = a4.RedInvm(); + Assert.AreEqual(a4.RedMul(b4).FromRed().ToString(16), "1"); + + //should invm numbers (regression) + var p5 = new BN( + "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", + 16); + var a5 = new BN( + "e1d969b8192fbac73ea5b7921896d6a2263d4d4077bb8e5055361d1f7f8163f3", + 16); + + var m5 = fn(p5); + a5 = a5.ToRed(m5); + + Assert.AreEqual(a5.RedInvm().FromRed().Negative, 0); + + //should imul numbers + var p6 = new BN( + "fffffffffffffffffffffffffffffffeffffffffffffffff", + 16); + var m6 = fn(p6); + + var a6 = new BN("deadbeefabbadead", 16); + var b6 = new BN("abbadeadbeefdead", 16); + var c6 = a6.Mul(b6).Mod(p6); + + Assert.AreEqual(a6.ToRed(m6).RedIMul(b6.ToRed(m6)).FromRed().ToString(16), + c6.ToString(16)); + + //should pow(base, 0) == 1 + var base1 = new BN(256).ToRed(BN.Red("k256")); + var exponent1 = new BN(0); + var result1 = base1.RedPow(exponent1); + Assert.AreEqual(result1.ToString(), "1"); + + //should shl numbers + var base2 = new BN(256).ToRed(BN.Red("k256")); + var result2 = base2.RedShl(1); + Assert.AreEqual(result2.ToString(), "512"); + + //should reduce when converting to red + var p7 = new BN(257); + var m7 = fn(p7); + var a7 = new BN(5).ToRed(m7); + + Assert.DoesNotThrow(() => { + var b7 = a7.RedISub(new BN(512).ToRed(m7)); + b7.RedISub(new BN(512).ToRed(m7)); + }); + + //redNeg and zero value + var a8 = new BN(0).ToRed(BN.Red("k256")).RedNeg(); + Assert.AreEqual(a8.IsZero(), true); + + //should not allow modulus <= 1 + Assert.Throws(() => { + BN.Red(new BN(0)); + }); + + Assert.Throws(() => { + BN.Red(new BN(1)); + }); + + Assert.DoesNotThrow(() => { + BN.Red(new BN(2)); + }); + } + + [Test] + public void Plain() + { + RedTestMethod((num) => + { + return BN.Red(num); + }); + + Assert.Pass(); + } + + [Test] + public void Montgomery() + { + RedTestMethod((num) => + { + return BN.Mont(num); + }); + + Assert.Pass(); + } + + [Test] + public void PseudoMersennePrimes() + { + //should reduce numbers mod k256 + var p1 = BN.Prime("k256"); + + Assert.AreEqual(p1.Ireduce(new BN(0xdead)).ToString(16), "dead"); + Assert.AreEqual(p1.Ireduce(new BN("deadbeef", 16)).ToString(16), "deadbeef"); + + var num = new BN( + "fedcba9876543210fedcba9876543210dead" + + "fedcba9876543210fedcba9876543210dead", + 16); + var exp = num.Mod(p1.p).ToString(16); + Assert.AreEqual(p1.Ireduce(num).ToString(16), exp); + + var regr = new BN( + "f7e46df64c1815962bf7bc9c56128798" + + "3f4fcef9cb1979573163b477eab93959" + + "335dfb29ef07a4d835d22aa3b6797760" + + "70a8b8f59ba73d56d01a79af9", + 16); + exp = regr.Mod(p1.p).ToString(16); + + Assert.AreEqual(p1.Ireduce(regr).ToString(16), exp); + + //should not fail to invm number mod k256 + var regr2 = new BN( + "6c150c4aa9a8cf1934485d40674d4a7cd494675537bda36d49405c5d2c6f496f", 16); + regr2 = regr2.ToRed(BN.Red("k256")); + Assert.AreEqual(regr2.RedInvm().RedMul(regr2).FromRed().Cmpn(1), 0); + + //should correctly square the number + var p2 = BN.Prime("k256").p; + var red = BN.Red("k256"); + + var n2 = new BN( + "9cd8cb48c3281596139f147c1364a3ed" + + "e88d3f310fdb0eb98c924e599ca1b3c9", + 16); + var expected = n2.Sqr().Mod(p2); + var actual2 = n2.ToRed(red).RedSqr().FromRed(); + + Assert.AreEqual(actual2.ToString(16), expected.ToString(16)); + + //redISqr should return right result + var n3 = new BN("30f28939", 16); + var actual3 = n3.ToRed(BN.Red("k256")).RedISqr().FromRed(); + Assert.AreEqual(actual3.ToString(16), "95bd93d19520eb1"); + + Assert.Pass(); + } + + private BN Bits2Int(byte[] obits, BN q) + { + var bits = new BN(obits); + var shift = (obits.Length << 3) - q.BitLength(); + if (shift > 0) + { + bits.Ishrn(shift); + } + return bits; + } + + private byte[] StringToByteArray(string hex) + { + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); + } + + [Test] + public void ShouldAvoid410Regresion() + { + var t = StringToByteArray("aff1651e4cd6036d57aa8b2a05ccf1a9d5a40166340ecbbdc55" + + "be10b568aa0aa3d05ce9a2fcec9df8ed018e29683c6051cb83e" + + "46ce31ba4edb045356a8d0d80b"); + var g = new BN( + "5c7ff6b06f8f143fe8288433493e4769c4d988ace5be25a0e24809670" + + "716c613d7b0cee6932f8faa7c44d2cb24523da53fbe4f6ec3595892d1" + + "aa58c4328a06c46a15662e7eaa703a1decf8bbb2d05dbe2eb956c142a" + + "338661d10461c0d135472085057f3494309ffa73c611f78b32adbb574" + + "0c361c9f35be90997db2014e2ef5aa61782f52abeb8bd6432c4dd097b" + + "c5423b285dafb60dc364e8161f4a2a35aca3a10b1c4d203cc76a470a3" + + "3afdcbdd92959859abd8b56e1725252d78eac66e71ba9ae3f1dd24871" + + "99874393cd4d832186800654760e1e34c09e4d155179f9ec0dc4473f9" + + "96bdce6eed1cabed8b6f116f7ad9cf505df0f998e34ab27514b0ffe7", + 16); + var p = new BN( + "9db6fb5951b66bb6fe1e140f1d2ce5502374161fd6538df1648218642" + + "f0b5c48c8f7a41aadfa187324b87674fa1822b00f1ecf8136943d7c55" + + "757264e5a1a44ffe012e9936e00c1d3e9310b01c7d179805d3058b2a9" + + "f4bb6f9716bfe6117c6b5b3cc4d9be341104ad4a80ad6c94e005f4b99" + + "3e14f091eb51743bf33050c38de235567e1b34c3d6a5c0ceaa1a0f368" + + "213c3d19843d0b4b09dcb9fc72d39c8de41f1bf14d4bb4563ca283716" + + "21cad3324b6a2d392145bebfac748805236f5ca2fe92b871cd8f9c36d" + + "3292b5509ca8caa77a2adfc7bfd77dda6f71125a7456fea153e433256" + + "a2261c6a06ed3693797e7995fad5aabbcfbe3eda2741e375404ae25b", + 16); + var q = new BN("f2c3119374ce76c9356990b465374a17f23f9ed35089bd969f61c6dde" + + "9998c1f", 16); + var k = Bits2Int(t, q); + var expectedR = "89ec4bb1400eccff8e7d9aa515cd1de7803f2daff09693ee7fd1353e" + + "90a68307"; + var r = g.ToRed(BN.Mont(p)).RedPow(k).FromRed().Mod(q); + Assert.AreEqual(r.ToString(16), expectedR); + + Assert.Pass(); + } + + [Test] + public void K256SplitFor512BitsNumberShouldReturnEqualNumbers() + { + var red = BN.Red("k256"); + var input = new BN(1).Iushln(512).Subn(1); + Assert.AreEqual(input.BitLength(), 512); + var output = new BN(0); + red.prime.Split(input, output); + Assert.AreEqual(input.Cmp(output), 0); + + Assert.Pass(); + } + + [Test] + public void ImodShouldChangeHostObject () + { + var red = BN.Red(new BN(13)); + var a = new BN(2).ToRed(red); + var b = new BN(7).ToRed(red); + var c = a.RedIMul(b); + Assert.AreEqual(a.ToNumber(), 1); + Assert.AreEqual(c.ToNumber(), 1); + + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/BNSharp.Tests/BNSharp.Tests.csproj b/BNSharp.Tests/BNSharp.Tests.csproj new file mode 100644 index 0000000..f6baa09 --- /dev/null +++ b/BNSharp.Tests/BNSharp.Tests.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + + diff --git a/BNSharp.Tests/BNUtilsTests.cs b/BNSharp.Tests/BNUtilsTests.cs new file mode 100644 index 0000000..f445839 --- /dev/null +++ b/BNSharp.Tests/BNUtilsTests.cs @@ -0,0 +1,404 @@ +using NUnit.Framework; + +namespace BNSharp.Tests +{ + [TestFixture, Category("BNUtils")] + public class BNUtilsTests + { + [SetUp] + public void Setup() + { + } + + [Test] + public new void ToString() + { + //hex no padding + //should have same length as input + var hex = "1"; + for (var i = 1; i <= 128; i++) + { + var n = new BN(hex, 16); + Assert.AreEqual(n.ToString(16).Length, i); + hex = hex + "0"; + } + + //binary padding + //should have a length of 256 + var a1 = new BN(0); + + Assert.AreEqual(a1.ToString(2, 256).Length, 256); + + //hex padding + //should have length of 8 from leading 15 + var a2 = new BN("ffb9602", 16); + + Assert.AreEqual(a2.ToString("hex", 2).Length, 8); + + //should have length of 8 from leading zero + var a3 = new BN("fb9604", 16); + + Assert.AreEqual(a3.ToString("hex", 8).Length, 8); + + //should have length of 8 from leading zeros + var a4 = new BN(0); + + Assert.AreEqual(a4.ToString("hex", 8).Length, 8); + + //should have length of 64 from leading 15 + var a5 = new BN( + "ffb96ff654e61130ba8422f0debca77a0ea74ae5ea8bca9b54ab64aabf01003", + 16); + + Assert.AreEqual(a5.ToString("hex", 2).Length, 64); + + //should have length of 64 from leading zero + var a6 = new BN( + "fb96ff654e61130ba8422f0debca77a0ea74ae5ea8bca9b54ab64aabf01003", + 16); + + Assert.AreEqual(a6.ToString("hex", 64).Length, 64); + + Assert.Pass(); + } + + [Test] + public void IsNeg() + { + //should return true for negative numbers + Assert.AreEqual(new BN(-1).IsNeg(), true); + Assert.AreEqual(new BN(1).IsNeg(), false); + Assert.AreEqual(new BN(0).IsNeg(), false); + Assert.AreEqual(new BN("-0", 10).IsNeg(), false); + + Assert.Pass(); + } + + [Test] + public void IsOdd() + { + //should return true for odd numbers + Assert.AreEqual(new BN(0).IsOdd(), false); + Assert.AreEqual(new BN(1).IsOdd(), true); + Assert.AreEqual(new BN(2).IsOdd(), false); + Assert.AreEqual(new BN("-0", 10).IsOdd(), false); + Assert.AreEqual(new BN("-1", 10).IsOdd(), true); + Assert.AreEqual(new BN("-2", 10).IsOdd(), false); + + Assert.Pass(); + } + + [Test] + public void IsEven() + { + //should return true for even numbers + Assert.AreEqual(new BN(0).IsEven(), true); + Assert.AreEqual(new BN(1).IsEven(), false); + Assert.AreEqual(new BN(2).IsEven(), true); + Assert.AreEqual(new BN("-0", 10).IsEven(), true); + Assert.AreEqual(new BN("-1", 10).IsEven(), false); + Assert.AreEqual(new BN("-2", 10).IsEven(), true); + + Assert.Pass(); + } + + [Test] + public void IsZero() + { + //should return true for zero + Assert.AreEqual(new BN(0).IsZero(), true); + Assert.AreEqual(new BN(1).IsZero(), false); + Assert.AreEqual(new BN(0xffffffff).IsZero(), false); + + Assert.Pass(); + } + + [Test] + public void BitLength() + { + //should return proper bitLength + Assert.AreEqual(new BN(0).BitLength(), 0); + Assert.AreEqual(new BN(0x1).BitLength(), 1); + Assert.AreEqual(new BN(0x2).BitLength(), 2); + Assert.AreEqual(new BN(0x3).BitLength(), 2); + Assert.AreEqual(new BN(0x4).BitLength(), 3); + Assert.AreEqual(new BN(0x8).BitLength(), 4); + Assert.AreEqual(new BN(0x10).BitLength(), 5); + Assert.AreEqual(new BN(0x100).BitLength(), 9); + Assert.AreEqual(new BN(0x123456).BitLength(), 21); + Assert.AreEqual(new BN("123456789", 16).BitLength(), 33); + Assert.AreEqual(new BN("8023456789", 16).BitLength(), 40); + + Assert.Pass(); + } + + [Test] + public void ByteLength() + { + //should return proper byteLength + Assert.AreEqual(new BN(0).ByteLength(), 0); + Assert.AreEqual(new BN(0x1).ByteLength(), 1); + Assert.AreEqual(new BN(0x2).ByteLength(), 1); + Assert.AreEqual(new BN(0x3).ByteLength(), 1); + Assert.AreEqual(new BN(0x4).ByteLength(), 1); + Assert.AreEqual(new BN(0x8).ByteLength(), 1); + Assert.AreEqual(new BN(0x10).ByteLength(), 1); + Assert.AreEqual(new BN(0x100).ByteLength(), 2); + Assert.AreEqual(new BN(0x123456).ByteLength(), 3); + Assert.AreEqual(new BN("123456789", 16).ByteLength(), 5); + Assert.AreEqual(new BN("8023456789", 16).ByteLength(), 5); + + Assert.Pass(); + } + + [Test] + public void ToArray() + { + //should return [ 0 ] for `0` + var n1 = new BN(0); + CollectionAssert.AreEqual(n1.ToArray(Endian.BigEndian), new byte[] { 0 }); + CollectionAssert.AreEqual(n1.ToArray(Endian.LittleEndian), new byte[] { 0 }); + + //should zero pad to desired lengths + var n2 = new BN(0x123456); + CollectionAssert.AreEqual(n2.ToArray(Endian.BigEndian, 5), new byte[] { 0x00, 0x00, 0x12, 0x34, 0x56 }); + CollectionAssert.AreEqual(n2.ToArray(Endian.LittleEndian, 5), new byte[] { 0x56, 0x34, 0x12, 0x00, 0x00 }); + + //should throw when naturally larger than desired length + var n3 = new BN(0x123456); + Assert.Throws(() => { + n3.ToArray(Endian.BigEndian, 2); + }); + + Assert.Pass(); + } + + [Test] + public void ToNumber() + { + //should return proper Number if below the limit + Assert.AreEqual(new BN(0x123456).ToNumber(), 0x123456); + Assert.AreEqual(new BN(0x3ffffff).ToNumber(), 0x3ffffff); + Assert.AreEqual(new BN(0x4000000).ToNumber(), 0x4000000); + Assert.AreEqual(new BN(0x10000000000000).ToNumber(), 0x10000000000000); + Assert.AreEqual(new BN(0x10040004004000).ToNumber(), 0x10040004004000); + Assert.AreEqual(new BN(-0x123456).ToNumber(), -0x123456); + Assert.AreEqual(new BN(-0x3ffffff).ToNumber(), -0x3ffffff); + Assert.AreEqual(new BN(-0x4000000).ToNumber(), -0x4000000); + Assert.AreEqual(new BN(-0x10000000000000).ToNumber(), -0x10000000000000); + Assert.AreEqual(new BN(-0x10040004004000).ToNumber(), -0x10040004004000); + + //should throw when number exceeds 53 bits + var n = new BN(1).Iushln(54); + Assert.Throws(() => { + n.ToNumber(); + }); + + Assert.Pass(); + } + + [Test] + public void ZeroBits() + { + //should return proper zeroBits + Assert.AreEqual(new BN(0).ZeroBits(), 0); + Assert.AreEqual(new BN(0x1).ZeroBits(), 0); + Assert.AreEqual(new BN(0x2).ZeroBits(), 1); + Assert.AreEqual(new BN(0x3).ZeroBits(), 0); + Assert.AreEqual(new BN(0x4).ZeroBits(), 2); + Assert.AreEqual(new BN(0x8).ZeroBits(), 3); + Assert.AreEqual(new BN(0x10).ZeroBits(), 4); + Assert.AreEqual(new BN(0x100).ZeroBits(), 8); + Assert.AreEqual(new BN(0x1000000).ZeroBits(), 24); + Assert.AreEqual(new BN(0x123456).ZeroBits(), 1); + + Assert.Pass(); + } + + [Test] + public void ToJSON() + { + //should return hex string + Assert.AreEqual(new BN(0x123).ToJSON(), "0123"); + + //should be padded to multiple of 2 bytes for interop + Assert.AreEqual(new BN(0x1).ToJSON(), "01"); + + Assert.Pass(); + } + + [Test] + public void Cmpn() + { + //should return -1, 0, 1 correctly + Assert.AreEqual(new BN(42).Cmpn(42), 0); + Assert.AreEqual(new BN(42).Cmpn(43), -1); + Assert.AreEqual(new BN(42).Cmpn(41), 1); + Assert.AreEqual(new BN(0x3fffffe).Cmpn(0x3fffffe), 0); + Assert.AreEqual(new BN(0x3fffffe).Cmpn(0x3ffffff), -1); + Assert.AreEqual(new BN(0x3fffffe).Cmpn(0x3fffffd), 1); + Assert.Throws(() => { + new BN(0x3fffffe).Cmpn(0x4000000); + }); + Assert.AreEqual(new BN(42).Cmpn(-42), 1); + Assert.AreEqual(new BN(-42).Cmpn(42), -1); + Assert.AreEqual(new BN(-42).Cmpn(-42), 0); + //? + //Assert.AreEqual(1 / new BN(-42).cmpn(-42), Infinity); + + Assert.Pass(); + } + + [Test] + public void Cmp() + { + //should return -1, 0, 1 correctly + Assert.AreEqual(new BN(42).Cmp(new BN(42)), 0); + Assert.AreEqual(new BN(42).Cmp(new BN(43)), -1); + Assert.AreEqual(new BN(42).Cmp(new BN(41)), 1); + Assert.AreEqual(new BN(0x3fffffe).Cmp(new BN(0x3fffffe)), 0); + Assert.AreEqual(new BN(0x3fffffe).Cmp(new BN(0x3ffffff)), -1); + Assert.AreEqual(new BN(0x3fffffe).Cmp(new BN(0x3fffffd)), 1); + Assert.AreEqual(new BN(0x3fffffe).Cmp(new BN(0x4000000)), -1); + Assert.AreEqual(new BN(42).Cmp(new BN(-42)), 1); + Assert.AreEqual(new BN(-42).Cmp(new BN(42)), -1); + Assert.AreEqual(new BN(-42).Cmp(new BN(-42)), 0); + //? + //Assert.AreEqual(1 / new BN(-42).cmp(new BN(-42)), Infinity); + + Assert.Pass(); + } + + [Test] + public void ComparisonShorthands() + { + //.gtn greater than + Assert.AreEqual(new BN(3).Gtn(2), true); + Assert.AreEqual(new BN(3).Gtn(3), false); + Assert.AreEqual(new BN(3).Gtn(4), false); + + //.gt greater than + Assert.AreEqual(new BN(3).Gt(new BN(2)), true); + Assert.AreEqual(new BN(3).Gt(new BN(3)), false); + Assert.AreEqual(new BN(3).Gt(new BN(4)), false); + + //.gten greater than or equal + Assert.AreEqual(new BN(3).Gten(3), true); + Assert.AreEqual(new BN(3).Gten(2), true); + Assert.AreEqual(new BN(3).Gten(4), false); + + //.gte greater than or equal + Assert.AreEqual(new BN(3).Gte(new BN(3)), true); + Assert.AreEqual(new BN(3).Gte(new BN(2)), true); + Assert.AreEqual(new BN(3).Gte(new BN(4)), false); + + //.ltn less than + Assert.AreEqual(new BN(2).Ltn(3), true); + Assert.AreEqual(new BN(2).Ltn(2), false); + Assert.AreEqual(new BN(2).Ltn(1), false); + + //.lt less than + Assert.AreEqual(new BN(2).Lt(new BN(3)), true); + Assert.AreEqual(new BN(2).Lt(new BN(2)), false); + Assert.AreEqual(new BN(2).Lt(new BN(1)), false); + + //.lten less than or equal + Assert.AreEqual(new BN(3).Lten(3), true); + Assert.AreEqual(new BN(3).Lten(2), false); + Assert.AreEqual(new BN(3).Lten(4), true); + + //.lte less than or equal + Assert.AreEqual(new BN(3).Lte(new BN(3)), true); + Assert.AreEqual(new BN(3).Lte(new BN(2)), false); + Assert.AreEqual(new BN(3).Lte(new BN(4)), true); + + //.eqn equal + Assert.AreEqual(new BN(3).Eqn(3), true); + Assert.AreEqual(new BN(3).Eqn(2), false); + Assert.AreEqual(new BN(3).Eqn(4), false); + + //.eq equal + Assert.AreEqual(new BN(3).Eq(new BN(3)), true); + Assert.AreEqual(new BN(3).Eq(new BN(2)), false); + Assert.AreEqual(new BN(3).Eq(new BN(4)), false); + + Assert.Pass(); + } + + [Test] + public void FromTwos() + { + //should convert from two\"s complement to negative number + Assert.AreEqual(new BN("00000000", 16).FromTwos(32).ToNumber(), 0); + Assert.AreEqual(new BN("00000001", 16).FromTwos(32).ToNumber(), 1); + Assert.AreEqual(new BN("7fffffff", 16).FromTwos(32).ToNumber(), 2147483647); + Assert.AreEqual(new BN("80000000", 16).FromTwos(32).ToNumber(), -2147483648); + Assert.AreEqual(new BN("f0000000", 16).FromTwos(32).ToNumber(), -268435456); + Assert.AreEqual(new BN("f1234567", 16).FromTwos(32).ToNumber(), -249346713); + Assert.AreEqual(new BN("ffffffff", 16).FromTwos(32).ToNumber(), -1); + Assert.AreEqual(new BN("fffffffe", 16).FromTwos(32).ToNumber(), -2); + Assert.AreEqual(new BN("fffffffffffffffffffffffffffffffe", 16) + .FromTwos(128).ToNumber(), -2); + Assert.AreEqual(new BN("ffffffffffffffffffffffffffffffff" + + "fffffffffffffffffffffffffffffffe", 16).FromTwos(256).ToNumber(), -2); + Assert.AreEqual(new BN("ffffffffffffffffffffffffffffffff" + + "ffffffffffffffffffffffffffffffff", 16).FromTwos(256).ToNumber(), -1); + Assert.AreEqual( + new BN("7fffffffffffffffffffffffffffffff" + + "ffffffffffffffffffffffffffffffff", 16).FromTwos(256).ToString(10), + new BN("5789604461865809771178549250434395392663499" + + "2332820282019728792003956564819967", 10).ToString(10)); + Assert.AreEqual( + new BN("80000000000000000000000000000000" + + "00000000000000000000000000000000", 16).FromTwos(256).ToString(10), + new BN("-578960446186580977117854925043439539266349" + + "92332820282019728792003956564819968", 10).ToString(10)); + + Assert.Pass(); + } + + [Test] + public void ToTwos() + { + //should convert from negative number to two\"s complement + Assert.AreEqual(new BN(0).ToTwos(32).ToString(16), "0"); + Assert.AreEqual(new BN(1).ToTwos(32).ToString(16), "1"); + Assert.AreEqual(new BN(2147483647).ToTwos(32).ToString(16), "7fffffff"); + Assert.AreEqual(new BN("-2147483648", 10).ToTwos(32).ToString(16), "80000000"); + Assert.AreEqual(new BN("-268435456", 10).ToTwos(32).ToString(16), "f0000000"); + Assert.AreEqual(new BN("-249346713", 10).ToTwos(32).ToString(16), "f1234567"); + Assert.AreEqual(new BN("-1", 10).ToTwos(32).ToString(16), "ffffffff"); + Assert.AreEqual(new BN("-2", 10).ToTwos(32).ToString(16), "fffffffe"); + Assert.AreEqual(new BN("-2", 10).ToTwos(128).ToString(16), + "fffffffffffffffffffffffffffffffe"); + Assert.AreEqual(new BN("-2", 10).ToTwos(256).ToString(16), + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"); + Assert.AreEqual(new BN("-1", 10).ToTwos(256).ToString(16), + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + Assert.AreEqual( + new BN("5789604461865809771178549250434395392663" + + "4992332820282019728792003956564819967", 10).ToTwos(256).ToString(16), + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + Assert.AreEqual( + new BN("-578960446186580977117854925043439539266" + + "34992332820282019728792003956564819968", 10).ToTwos(256).ToString(16), + "8000000000000000000000000000000000000000000000000000000000000000"); + + Assert.Pass(); + } + + [Test] + public void IsBN() + { + //should return true for BN + Assert.AreEqual(BN.IsBN(new BN()), true); + + //should return false for everything else + Assert.AreEqual(BN.IsBN(1), false); + Assert.AreEqual(BN.IsBN(new byte[0]), false); + Assert.AreEqual(BN.IsBN(new { }), false); + + Assert.Pass(); + } + } +} \ No newline at end of file diff --git a/BNSharp.Tests/Extensions/NumberExtensions.cs b/BNSharp.Tests/Extensions/NumberExtensions.cs new file mode 100644 index 0000000..13b416b --- /dev/null +++ b/BNSharp.Tests/Extensions/NumberExtensions.cs @@ -0,0 +1,55 @@ +using System; + +namespace BNSharp.Tests.Extensions +{ + internal static class NumberExtensions + { + public static string ToString(this int num, int @base) + { + return DecimalToArbitrarySystem(num, @base); + } + + public static string ToString(this long num, int @base) + { + return DecimalToArbitrarySystem(num, @base); + } + + /// + /// Converts the given decimal number to the numeral system with the + /// specified radix (in the range [2, 36]). + /// + /// The number to convert. + /// The radix of the destination numeral system (in the range [2, 36]). + /// + private static string DecimalToArbitrarySystem(long decimalNumber, int radix) + { + const int BitsInLong = 64; + const string Digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + if (radix < 2 || radix > Digits.Length) + throw new ArgumentException("The radix must be >= 2 and <= " + Digits.Length.ToString()); + + if (decimalNumber == 0) + return "0"; + + int index = BitsInLong - 1; + long currentNumber = Math.Abs(decimalNumber); + char[] charArray = new char[BitsInLong]; + + while (currentNumber != 0) + { + int remainder = (int)(currentNumber % radix); + charArray[index--] = Digits[remainder]; + currentNumber = currentNumber / radix; + } + + string result = new string(charArray, index + 1, BitsInLong - index - 1); + if (decimalNumber < 0) + { + result = "-" + result; + } + + return result.ToLowerInvariant(); + } + } +} diff --git a/BNSharp.Tests/Fixtures.cs b/BNSharp.Tests/Fixtures.cs new file mode 100644 index 0000000..0204c96 --- /dev/null +++ b/BNSharp.Tests/Fixtures.cs @@ -0,0 +1,309 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BNSharp.Tests +{ + internal class Fixtures + { + public static Dictionary> DhGroups = new() + { + { + "p16", + new Dictionary() + { + { + "prime", + "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd1" + + "29024e088a67cc74020bbea63b139b22514a08798e3404dd" + + "ef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245" + + "e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" + + "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3d" + + "c2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f" + + "83655d23dca3ad961c62f356208552bb9ed529077096966d" + + "670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" + + "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9" + + "de2bcbf6955817183995497cea956ae515d2261898fa0510" + + "15728e5a8aaac42dad33170d04507a33a85521abdf1cba64" + + "ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7" + + "abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6b" + + "f12ffa06d98a0864d87602733ec86a64521f2b18177b200c" + + "bbe117577a615d6c770988c0bad946e208e24fa074e5ab31" + + "43db5bfce0fd108e4b82d120a92108011a723c12a787e6d7" + + "88719a10bdba5b2699c327186af4e23c1a946834b6150bda" + + "2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6" + + "287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed" + + "1f612970cee2d7afb81bdd762170481cd0069127d5b05aa9" + + "93b4ea988d8fddc186ffb7dc90a6c08f4df435c934063199" + + "ffffffffffffffff" + }, + { + "priv", + "6d5923e6449122cbbcc1b96093e0b7e4fd3e469f58daddae" + + "53b49b20664f4132675df9ce98ae0cfdcac0f4181ccb643b" + + "625f98104dcf6f7d8e81961e2cab4b5014895260cb977c7d" + + "2f981f8532fb5da60b3676dfe57f293f05d525866053ac7e" + + "65abfd19241146e92e64f309a97ef3b529af4d6189fa416c" + + "9e1a816c3bdf88e5edf48fbd8233ef9038bb46faa95122c0" + + "5a426be72039639cd2d53d37254b3d258960dcb33c255ede" + + "20e9d7b4b123c8b4f4b986f53cdd510d042166f7dd7dca98" + + "7c39ab36381ba30a5fdd027eb6128d2ef8e5802a2194d422" + + "b05fe6e1cb4817789b923d8636c1ec4b7601c90da3ddc178" + + "52f59217ae070d87f2e75cbfb6ff92430ad26a71c8373452" + + "ae1cc5c93350e2d7b87e0acfeba401aaf518580937bf0b6c" + + "341f8c49165a47e49ce50853989d07171c00f43dcddddf72" + + "94fb9c3f4e1124e98ef656b797ef48974ddcd43a21fa06d0" + + "565ae8ce494747ce9e0ea0166e76eb45279e5c6471db7df8" + + "cc88764be29666de9c545e72da36da2f7a352fb17bdeb982" + + "a6dc0193ec4bf00b2e533efd6cd4d46e6fb237b775615576" + + "dd6c7c7bbc087a25e6909d1ebc6e5b38e5c8472c0fc429c6" + + "f17da1838cbcd9bbef57c5b5522fd6053e62ba21fe97c826" + + "d3889d0cc17e5fa00b54d8d9f0f46fb523698af965950f4b" + + "941369e180f0aece3870d9335f2301db251595d173902cad" + + "394eaa6ffef8be6c" + }, + { + "pub", + "d53703b7340bc89bfc47176d351e5cf86d5a18d9662eca3c" + + "9759c83b6ccda8859649a5866524d77f79e501db923416ca" + + "2636243836d3e6df752defc0fb19cc386e3ae48ad647753f" + + "bf415e2612f8a9fd01efe7aca249589590c7e6a0332630bb" + + "29c5b3501265d720213790556f0f1d114a9e2071be3620bd" + + "4ee1e8bb96689ac9e226f0a4203025f0267adc273a43582b" + + "00b70b490343529eaec4dcff140773cd6654658517f51193" + + "13f21f0a8e04fe7d7b21ffeca85ff8f87c42bb8d9cb13a72" + + "c00e9c6e9dfcedda0777af951cc8ccab90d35e915e707d8e" + + "4c2aca219547dd78e9a1a0730accdc9ad0b854e51edd1e91" + + "4756760bab156ca6e3cb9c625cf0870def34e9ac2e552800" + + "d6ce506d43dbbc75acfa0c8d8fb12daa3c783fb726f187d5" + + "58131779239c912d389d0511e0f3a81969d12aeee670e48f" + + "ba41f7ed9f10705543689c2506b976a8ffabed45e33795b0" + + "1df4f6b993a33d1deab1316a67419afa31fbb6fdd252ee8c" + + "7c7d1d016c44e3fcf6b41898d7f206aa33760b505e4eff2e" + + "c624bc7fe636b1d59e45d6f904fc391419f13d1f0cdb5b6c" + + "2378b09434159917dde709f8a6b5dc30994d056e3f964371" + + "11587ac7af0a442b8367a7bd940f752ddabf31cf01171e24" + + "d78df136e9681cd974ce4f858a5fb6efd3234a91857bb52d" + + "9e7b414a8bc66db4b5a73bbeccfb6eb764b4f0cbf0375136" + + "b024b04e698d54a5" + } + } + }, + { + "p17", + new Dictionary() + { + { + "prime", + "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd1" + + "29024e088a67cc74020bbea63b139b22514a08798e3404dd" + + "ef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245" + + "e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" + + "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3d" + + "c2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f" + + "83655d23dca3ad961c62f356208552bb9ed529077096966d" + + "670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" + + "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9" + + "de2bcbf6955817183995497cea956ae515d2261898fa0510" + + "15728e5a8aaac42dad33170d04507a33a85521abdf1cba64" + + "ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7" + + "abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6b" + + "f12ffa06d98a0864d87602733ec86a64521f2b18177b200c" + + "bbe117577a615d6c770988c0bad946e208e24fa074e5ab31" + + "43db5bfce0fd108e4b82d120a92108011a723c12a787e6d7" + + "88719a10bdba5b2699c327186af4e23c1a946834b6150bda" + + "2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6" + + "287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed" + + "1f612970cee2d7afb81bdd762170481cd0069127d5b05aa9" + + "93b4ea988d8fddc186ffb7dc90a6c08f4df435c934028492" + + "36c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bd" + + "f8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831" + + "179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1b" + + "db7f1447e6cc254b332051512bd7af426fb8f401378cd2bf" + + "5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6" + + "d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f3" + + "23a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aa" + + "cc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be328" + + "06a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55c" + + "da56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee" + + "12bf2d5b0b7474d6e694f91e6dcc4024ffffffffffffffff" + }, + { + "priv", + "6017f2bc23e1caff5b0a8b4e1fc72422b5204415787801dc" + + "025762b8dbb98ab57603aaaa27c4e6bdf742b4a1726b9375" + + "a8ca3cf07771779589831d8bd18ddeb79c43e7e77d433950" + + "e652e49df35b11fa09644874d71d62fdaffb580816c2c88c" + + "2c4a2eefd4a660360316741b05a15a2e37f236692ad3c463" + + "fff559938fc6b77176e84e1bb47fb41af691c5eb7bb81bd8" + + "c918f52625a1128f754b08f5a1403b84667231c4dfe07ed4" + + "326234c113931ce606037e960f35a2dfdec38a5f057884d3" + + "0af8fab3be39c1eeb390205fd65982191fc21d5aa30ddf51" + + "a8e1c58c0c19fc4b4a7380ea9e836aaf671c90c29bc4bcc7" + + "813811aa436a7a9005de9b507957c56a9caa1351b6efc620" + + "7225a18f6e97f830fb6a8c4f03b82f4611e67ab9497b9271" + + "d6ac252793cc3e5538990dbd894d2dbc2d152801937d9f74" + + "da4b741b50b4d40e4c75e2ac163f7b397fd555648b249f97" + + "ffe58ffb6d096aa84534c4c5729cff137759bd34e80db4ab" + + "47e2b9c52064e7f0bf677f72ac9e5d0c6606943683f9d12f" + + "180cf065a5cb8ec3179a874f358847a907f8471d15f1e728" + + "7023249d6d13c82da52628654438f47b8b5cdf4761fbf6ad" + + "9219eceac657dbd06cf2ab776ad4c968f81c3d039367f0a4" + + "d77c7ec4435c27b6c147071665100063b5666e06eb2fb2cc" + + "3159ba34bc98ca346342195f6f1fb053ddc3bc1873564d40" + + "1c6738cdf764d6e1ff25ca5926f80102ea6593c17170966b" + + "b5d7352dd7fb821230237ea3ebed1f920feaadbd21be295a" + + "69f2083deae9c5cdf5f4830eb04b7c1f80cc61c17232d79f" + + "7ecc2cc462a7965f804001c89982734e5abba2d31df1b012" + + "152c6b226dff34510b54be8c2cd68d795def66c57a3abfb6" + + "896f1d139e633417f8c694764974d268f46ece3a8d6616ea" + + "a592144be48ee1e0a1595d3e5edfede5b27cec6c48ceb2ff" + + "b42cb44275851b0ebf87dfc9aa2d0cb0805e9454b051dfe8" + + "a29fadd82491a4b4c23f2d06ba45483ab59976da1433c9ce" + + "500164b957a04cf62dd67595319b512fc4b998424d1164dd" + + "bbe5d1a0f7257cbb04ec9b5ed92079a1502d98725023ecb2" + }, + { + "pub", + "3bf836229c7dd874fe37c1790d201e82ed8e192ed61571ca" + + "7285264974eb2a0171f3747b2fc23969a916cbd21e14f7e2" + + "f0d72dcd2247affba926f9e7bb99944cb5609aed85e71b89" + + "e89d2651550cb5bd8281bd3144066af78f194032aa777739" + + "cccb7862a1af401f99f7e5c693f25ddce2dedd9686633820" + + "d28d0f5ed0c6b5a094f5fe6170b8e2cbc9dff118398baee6" + + "e895a6301cb6e881b3cae749a5bdf5c56fc897ff68bc73f2" + + "4811bb108b882872bade1f147d886a415cda2b93dd90190c" + + "be5c2dd53fe78add5960e97f58ff2506afe437f4cf4c912a" + + "397c1a2139ac6207d3ab76e6b7ffd23bb6866dd7f87a9ae5" + + "578789084ff2d06ea0d30156d7a10496e8ebe094f5703539" + + "730f5fdbebc066de417be82c99c7da59953071f49da7878d" + + "a588775ff2a7f0084de390f009f372af75cdeba292b08ea8" + + "4bd13a87e1ca678f9ad148145f7cef3620d69a891be46fbb" + + "cad858e2401ec0fd72abdea2f643e6d0197b7646fbb83220" + + "0f4cf7a7f6a7559f9fb0d0f1680822af9dbd8dec4cd1b5e1" + + "7bc799e902d9fe746ddf41da3b7020350d3600347398999a" + + "baf75d53e03ad2ee17de8a2032f1008c6c2e6618b62f225b" + + "a2f350179445debe68500fcbb6cae970a9920e321b468b74" + + "5fb524fb88abbcacdca121d737c44d30724227a99745c209" + + "b970d1ff93bbc9f28b01b4e714d6c9cbd9ea032d4e964d8e" + + "8fff01db095160c20b7646d9fcd314c4bc11bcc232aeccc0" + + "fbedccbc786951025597522eef283e3f56b44561a0765783" + + "420128638c257e54b972a76e4261892d81222b3e2039c61a" + + "ab8408fcaac3d634f848ab3ee65ea1bd13c6cd75d2e78060" + + "e13cf67fbef8de66d2049e26c0541c679fff3e6afc290efe" + + "875c213df9678e4a7ec484bc87dae5f0a1c26d7583e38941" + + "b7c68b004d4df8b004b666f9448aac1cc3ea21461f41ea5d" + + "d0f7a9e6161cfe0f58bcfd304bdc11d78c2e9d542e86c0b5" + + "6985cc83f693f686eaac17411a8247bf62f5ccc7782349b5" + + "cc1f20e312fa2acc0197154d1bfee507e8db77e8f2732f2d" + + "641440ccf248e8643b2bd1e1f9e8239356ab91098fcb431d" + }, + { + "q", + "a899c59999bf877d96442d284359783bdc64b5f878b688fe" + + "51407f0526e616553ad0aaaac4d5bed3046f10a1faaf42bb" + + "2342dc4b7908eea0c46e4c4576897675c2bfdc4467870d3d" + + "cd90adaed4359237a4bc6924bfb99aa6bf5f5ede15b574ea" + + "e977eac096f3c67d09bda574c6306c6123fa89d2f086b8dc" + + "ff92bc570c18d83fe6c810ccfd22ce4c749ef5e6ead3fffe" + + "c63d95e0e3fde1df9db6a35fa1d107058f37e41957769199" + + "d945dd7a373622c65f0af3fd9eb1ddc5c764bbfaf7a3dc37" + + "2548e683b970dac4aa4b9869080d2376c9adecebb84e172c" + + "09aeeb25fb8df23e60033260c4f8aac6b8b98ab894b1fb84" + + "ebb83c0fb2081c3f3eee07f44e24d8fabf76f19ed167b0d7" + + "ff971565aa4efa3625fce5a43ceeaa3eebb3ce88a00f597f" + + "048c69292b38dba2103ecdd5ec4ccfe3b2d87fa6202f334b" + + "c1cab83b608dfc875b650b69f2c7e23c0b2b4adf149a6100" + + "db1b6dbad4679ecb1ea95eafaba3bd00db11c2134f5a8686" + + "358b8b2ab49a1b2e85e1e45caeac5cd4dc0b3b5fffba8871" + + "1c6baf399edd48dad5e5c313702737a6dbdcede80ca358e5" + + "1d1c4fe42e8948a084403f61baed38aa9a1a5ce2918e9f33" + + "100050a430b47bc592995606440272a4994677577a6aaa1b" + + "a101045dbec5a4e9566dab5445d1af3ed19519f07ac4e2a8" + + "bd0a84b01978f203a9125a0be020f71fab56c2c9e344d4f4" + + "12d53d3cd8eb74ca5122002e931e3cb0bd4b7492436be17a" + + "d7ebe27148671f59432c36d8c56eb762655711cfc8471f70" + + "83a8b7283bcb3b1b1d47d37c23d030288cfcef05fbdb4e16" + + "652ee03ee7b77056a808cd700bc3d9ef826eca9a59be959c" + + "947c865d6b372a1ca2d503d7df6d7611b12111665438475a" + + "1c64145849b3da8c2d343410df892d958db232617f9896f1" + + "de95b8b5a47132be80dd65298c7f2047858409bf762dbc05" + + "a62ca392ac40cfb8201a0607a2cae07d99a307625f2b2d04" + + "fe83fbd3ab53602263410f143b73d5b46fc761882e78c782" + + "d2c36e716a770a7aefaf7f76cea872db7bffefdbc4c2f9e0" + + "39c19adac915e7a63dcb8c8c78c113f29a3e0bc10e100ce0" + }, + { + "qs", + "6f0a2fb763eaeb8eb324d564f03d4a55fdcd709e5f1b65e9" + + "5702b0141182f9f945d71bc3e64a7dfdae7482a7dd5a4e58" + + "bc38f78de2013f2c468a621f08536969d2c8d011bb3bc259" + + "2124692c91140a5472cad224acdacdeae5751dadfdf068b8" + + "77bfa7374694c6a7be159fc3d24ff9eeeecaf62580427ad8" + + "622d48c51a1c4b1701d768c79d8c819776e096d2694107a2" + + "f3ec0c32224795b59d32894834039dacb369280afb221bc0" + + "90570a93cf409889b818bb30cccee98b2aa26dbba0f28499" + + "08e1a3cd43fa1f1fb71049e5c77c3724d74dc351d9989057" + + "37bbda3805bd6b1293da8774410fb66e3194e18cdb304dd9" + + "a0b59b583dcbc9fc045ac9d56aea5cfc9f8a0b95da1e11b7" + + "574d1f976e45fe12294997fac66ca0b83fc056183549e850" + + "a11413cc4abbe39a211e8c8cbf82f2a23266b3c10ab9e286" + + "07a1b6088909cddff856e1eb6b2cde8bdac53fa939827736" + + "ca1b892f6c95899613442bd02dbdb747f02487718e2d3f22" + + "f73734d29767ed8d0e346d0c4098b6fdcb4df7d0c4d29603" + + "5bffe80d6c65ae0a1b814150d349096baaf950f2caf298d2" + + "b292a1d48cf82b10734fe8cedfa16914076dfe3e9b51337b" + + "ed28ea1e6824bb717b641ca0e526e175d3e5ed7892aebab0" + + "f207562cc938a821e2956107c09b6ce4049adddcd0b7505d" + + "49ae6c69a20122461102d465d93dc03db026be54c303613a" + + "b8e5ce3fd4f65d0b6162ff740a0bf5469ffd442d8c509cd2" + + "3b40dab90f6776ca17fc0678774bd6eee1fa85ababa52ec1" + + "a15031eb677c6c488661dddd8b83d6031fe294489ded5f08" + + "8ad1689a14baeae7e688afa3033899c81f58de39b392ca94" + + "af6f15a46f19fa95c06f9493c8b96a9be25e78b9ea35013b" + + "caa76de6303939299d07426a88a334278fc3d0d9fa71373e" + + "be51d3c1076ab93a11d3d0d703366ff8cde4c11261d488e5" + + "60a2bdf3bfe2476032294800d6a4a39d306e65c6d7d8d66e" + + "5ec63eee94531e83a9bddc458a2b508285c0ee10b7bd94da" + + "2815a0c5bd5b2e15cbe66355e42f5af8955cdfc0b3a4996d" + + "288db1f4b32b15643b18193e378cb7491f3c3951cdd044b1" + + "a519571bffac2da986f5f1d506c66530a55f70751e24fa8e" + + "d83ac2347f4069fb561a5565e78c6f0207da24e889a93a96" + + "65f717d9fe8a2938a09ab5f81be7ccecf466c0397fc15a57" + + "469939793f302739765773c256a3ca55d0548afd117a7cae" + + "98ca7e0d749a130c7b743d376848e255f8fdbe4cb4480b63" + + "cd2c015d1020cf095d175f3ca9dcdfbaf1b2a6e6468eee4c" + + "c750f2132a77f376bd9782b9d0ff4da98621b898e251a263" + + "4301ba2214a8c430b2f7a79dbbfd6d7ff6e9b0c137b025ff" + + "587c0bf912f0b19d4fff96b1ecd2ca990c89b386055c60f2" + + "3b94214bd55096f17a7b2c0fa12b333235101cd6f28a128c" + + "782e8a72671adadebbd073ded30bd7f09fb693565dcf0bf3" + + "090c21d13e5b0989dd8956f18f17f4f69449a13549c9d80a" + + "77e5e61b5aeeee9528634100e7bc390672f0ded1ca53555b" + + "abddbcf700b9da6192255bddf50a76b709fbed251dce4c7e" + + "1ca36b85d1e97c1bc9d38c887a5adf140f9eeef674c31422" + + "e65f63cae719f8c1324e42fa5fd8500899ef5aa3f9856aa7" + + "ce10c85600a040343204f36bfeab8cfa6e9deb8a2edd2a8e" + + "018d00c7c9fa3a251ad0f57183c37e6377797653f382ec7a" + + "2b0145e16d3c856bc3634b46d90d7198aff12aff88a30e34" + + "e2bfaf62705f3382576a9d3eeb0829fca2387b5b654af46e" + + "5cf6316fb57d59e5ea6c369061ac64d99671b0e516529dd5" + + "d9c48ea0503e55fee090d36c5ea8b5954f6fcc0060794e1c" + + "b7bc24aa1e5c0142fd4ce6e8fd5aa92a7bf84317ea9e1642" + + "b6995bac6705adf93cbce72433ed0871139970d640f67b78" + + "e63a7a6d849db2567df69ac7d79f8c62664ac221df228289" + + "d0a4f9ebd9acb4f87d49da64e51a619fd3f3baccbd9feb12" + + "5abe0cc2c8d17ed1d8546da2b6c641f4d3020a5f9b9f26ac" + + "16546c2d61385505612275ea344c2bbf1ce890023738f715" + + "5e9eba6a071678c8ebd009c328c3eb643679de86e69a9fa5" + + "67a9e146030ff03d546310a0a568c5ba0070e0da22f2cef8" + + "54714b04d399bbc8fd261f9e8efcd0e83bdbc3f5cfb2d024" + + "3e398478cc598e000124eb8858f9df8f52946c2a1ca5c400" + } + } + } + }; + } +} diff --git a/BNSharp.sln b/BNSharp.sln new file mode 100644 index 0000000..79c85c7 --- /dev/null +++ b/BNSharp.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32407.343 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BNSharp", "BNSharp\BNSharp.csproj", "{8893EBC8-530F-4394-9528-229582651CD9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BNSharp.Tests", "BNSharp.Tests\BNSharp.Tests.csproj", "{CA1F604E-A9C1-4DE2-BBC6-DBBD87A6EC1A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8893EBC8-530F-4394-9528-229582651CD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8893EBC8-530F-4394-9528-229582651CD9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8893EBC8-530F-4394-9528-229582651CD9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8893EBC8-530F-4394-9528-229582651CD9}.Release|Any CPU.Build.0 = Release|Any CPU + {CA1F604E-A9C1-4DE2-BBC6-DBBD87A6EC1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA1F604E-A9C1-4DE2-BBC6-DBBD87A6EC1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA1F604E-A9C1-4DE2-BBC6-DBBD87A6EC1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA1F604E-A9C1-4DE2-BBC6-DBBD87A6EC1A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7C92B670-1CD6-4C9A-8EDB-A305CA33A798} + EndGlobalSection +EndGlobal diff --git a/BNSharp/BN.cs b/BNSharp/BN.cs new file mode 100644 index 0000000..7e97bd3 --- /dev/null +++ b/BNSharp/BN.cs @@ -0,0 +1,3984 @@ +using BNSharp.Extensions; +using System.Text.RegularExpressions; + +namespace BNSharp +{ + public partial class BN + { + internal static int wordSize = 26; + + public int Negative; + public int[] words; + public int Length; + internal Red? red; + + public BN() + { + this.Negative = 0; + this.words = null; + this.Length = 0; + + // Reduction context + this.red = null; + } + + public BN(BN number) : this() + { + number.Copy(this); + } + + /// + /// + /// + /// + /// + /// + public BN(long number, Endian endian = Endian.BigEndian) : this(number, 10, endian) + { + + } + + public BN(byte[]? number, Endian endian = Endian.BigEndian) : this(number, 10, endian) + { + + } + + /// + /// + /// + /// + /// + /// + public BN(string? number, Endian endian = Endian.BigEndian) : this(number, 10, endian) + { + + } + + /// + /// + /// + /// + /// + /// + /// + public BN(long number, string? @base, Endian endian = Endian.BigEndian) : this(number, _baseStringToInt(@base), endian) + { + + } + + public BN(byte[]? number, string? @base, Endian endian = Endian.BigEndian) : this(number, _baseStringToInt(@base), endian) + { + + } + + /// + /// + /// + /// + /// + /// + /// + public BN(string? number, string? @base, Endian endian = Endian.BigEndian) : this(number, _baseStringToInt(@base), endian) + { + + } + + public BN(byte[]? number, int @base, Endian endian = Endian.BigEndian) : this() + { + if (number != null) + { + this._init(number, (int)@base, endian); + } + } + + /// + /// + /// + /// + /// + /// + /// + public BN(long number, int @base, Endian endian = Endian.BigEndian) : this() + { + this._init(number, (int)@base, endian); + } + + /// + /// + /// + /// + /// + /// + /// + public BN(string? number, int @base, Endian endian = Endian.BigEndian) : this() + { + if (number != null) + { + this._init(number, (int)@base, endian); + } + } + + private static int _baseStringToInt(string? @base) + { + int baseInt = 10; + if (@base == "hex") + { + baseInt = 16; + } + + return baseInt; + } + + /// + /// + /// + /// + /// True if the supplied object is a BN instance + public static bool IsBN(object? number) + { + if (number is BN) { + return true; + } + + return false; + } + + /// + /// + /// + /// + /// + /// `left` if (`left` > `right`) + public static BN Max(BN left, BN right) + { + if (left.Cmp(right) > 0) return left; + return right; + } + + /// + /// + /// + /// + /// + /// `left` if (`left` < `right`) + public static BN Min(BN left, BN right) + { + if (left.Cmp(right) < 0) return left; + return right; + } + + private void _init(long number, int @base, Endian endian) + { + this._initNumber(number, @base, endian); + } + + private void _init(byte[] number, int @base, Endian endian) + { + this._initArray(number, @base, endian); + } + + private void _init(string number, int @base, Endian endian) + { + Functions.Assert(@base == (@base | 0) && @base >= 2 && @base <= 36); + + number = Regex.Replace(number.ToString(), @"\s+", ""); + + int start = 0; + if (number[0] == '-') + { + start++; + this.Negative = 1; + } + + if (start < number.Length) + { + if (@base == 16) + { + this._parseHex(number, start, endian); + } + else + { + this._parseBase(number, @base, start); + if (endian == Endian.LittleEndian) + { + this._initArray(this.ToArray(), @base, endian); + } + } + } + } + + private void _initNumber(long number, int @base, Endian endian) + { + if (number < 0) + { + this.Negative = 1; + number = -number; + } + if (number < 0x4000000) + { + this.words = new[] { (int)(number & 0x3ffffff) }; + this.Length = 1; + } + else if (number < 0x10000000000000) + { + this.words = new[] { + (int)(number & 0x3ffffff), + (int)((number / 0x4000000) & 0x3ffffff) + }; + this.Length = 2; + } + else + { + Functions.Assert(number < 0x20000000000000); // 2 ^ 53 (unsafe) + this.words = new[] { + (int)(number & 0x3ffffff), + (int)((number / 0x4000000) & 0x3ffffff), + 1 + }; + this.Length = 3; + } + + if (endian != Endian.LittleEndian) return; + + // Reverse the bytes + this._initArray(this.ToArray(), @base, endian); + } + + private void _initArray(byte[] number, int @base, Endian endian) + { + if (number.Length <= 0) + { + this.words = new int[] { 0 }; + this.Length = 1; + return; + } + + this.Length = (int)Math.Ceiling((double)number.Length / 3); + this.words = new int[this.Length]; + for (var i = 0; i < this.Length; i++) + { + this.words[i] = 0; + } + + uint w; + var off = 0; + if (endian == Endian.BigEndian) + { + for (int i = number.Length - 1, j = 0; i >= 0; i -= 3) + { + var first = number[i]; + var second = (i - 1 >= 0) ? (number[i - 1] << 8) : 0; + var third = (i - 2 >= 0) ? (number[i - 2] << 16) : 0; + w = (uint)(first | second | third); + this.words[j] |= (int)((w << off) & 0x3ffffff); + if (j + 1 >= this.words.Length) + { + Array.Resize(ref this.words, j + 2); + this.Length = j + 2; + } + this.words[j + 1] = (int)((w >> (26 - off)) & 0x3ffffff); + off += 24; + if (off >= 26) + { + off -= 26; + j++; + } + } + } + else if (endian == Endian.LittleEndian) + { + for (int i = 0, j = 0; i < number.Length; i += 3) + { + var first = number[i]; + var second = (i + 1 < number.Length) ? (number[i + 1] << 8) : 0; + var third = (i + 2 < number.Length) ? (number[i + 2] << 16) : 0; + w = (uint)(first | second | third); + this.words[j] |= (int)((w << off) & 0x3ffffff); + if (j + 1 >= this.words.Length) + { + Array.Resize(ref this.words, j + 2); + this.Length = j + 2; + } + this.words[j + 1] = (int)((w >> (26 - off)) & 0x3ffffff); + off += 24; + if (off >= 26) + { + off -= 26; + j++; + } + } + } + this._strip(); + } + + private byte parseHex4Bits(string @string, int index) + { + var c = @string.ElementAt(index); + // '0' - '9' + if (c >= 48 && c <= 57) + { + return (byte)(c - 48); + // 'A' - 'F' + } + else if (c >= 65 && c <= 70) + { + return (byte)(c - 55); + // 'a' - 'f' + } + else if (c >= 97 && c <= 102) + { + return (byte)(c - 87); + } + else + { + Functions.Assert(false, "Invalid character in " + @string); + return 0; + } + } + + private byte parseHexByte(string @string, int lowerBound, int index) + { + var r = parseHex4Bits(@string, index); + if (index - 1 >= lowerBound) + { + r |= (byte)(parseHex4Bits(@string, index - 1) << 4); + } + return r; + } + + private void _parseHex(string number, int start, Endian endian) + { + // Create possibly bigger array to ensure that it fits the number + this.Length = (int)Math.Ceiling((double)(number.Length - start) / 6); + this.words = new int[this.Length]; + int i; + for (i = 0; i < this.Length; i++) + { + this.words[i] = 0; + } + + // 24-bits chunks + var off = 0; + var j = 0; + + uint w; + if (endian == Endian.BigEndian) + { + for (i = number.Length - 1; i >= start; i -= 2) + { + w = (uint)(parseHexByte(number, start, i) << off); + this.words[j] |= (int)(w & 0x3ffffff); + if (off >= 18) + { + off -= 18; + j += 1; + this.words[j] |= (int)(w >> 26); + } + else + { + off += 8; + } + } + } + else + { + var parseLength = number.Length - start; + for (i = parseLength % 2 == 0 ? start + 1 : start; i < number.Length; i += 2) + { + w = (uint)(parseHexByte(number, start, i) << off); + this.words[j] |= (int)(w & 0x3ffffff); + if (off >= 18) + { + off -= 18; + j += 1; + this.words[j] |= (int)(w >> 26); + } + else + { + off += 8; + } + } + } + + this._strip(); + } + + private int parseBase(string str, int start, int end, int mul) + { + var r = 0; + var b = 0; + var len = Math.Min(str.Length, end); + for (var i = start; i < len; i++) + { + var c = str.ElementAt(i) - 48; + + r *= mul; + + // 'a' + if (c >= 49) + { + b = c - 49 + 0xa; + + // 'A' + } + else if (c >= 17) + { + b = c - 17 + 0xa; + + // '0' - '9' + } + else + { + b = c; + } + Functions.Assert(c >= 0 && b < mul, "Invalid character"); + r += b; + } + return r; + } + + private void _parseBase(string number, int @base, int start) + { + // Initialize as zero + this.words = new int[] { 0 }; + this.Length = 1; + + int limbLen; + long limbPow; + // Find length of limb in base + for (limbLen = 0, limbPow = 1; limbPow <= 0x3ffffff; limbPow *= @base) + { + limbLen++; + } + limbLen--; + limbPow = (limbPow / @base) | 0; + + var total = number.Length - start; + var mod = total % limbLen; + var end = Math.Min(total, total - mod) + start; + + var word = 0; + int i; + for (i = start; i < end; i += limbLen) + { + word = parseBase(number, i, i + limbLen, @base); + + this.Imuln(limbPow); + if (this.words[0] + word < 0x4000000) + { + this.words[0] += word; + } + else + { + this._iaddn(word); + } + } + + if (mod != 0) + { + var pow = 1; + word = parseBase(number, i, number.Length, @base); + + for (i = 0; i < mod; i++) + { + pow *= @base; + } + + this.Imuln(pow); + if (this.words[0] + word < 0x4000000) + { + this.words[0] += word; + } + else + { + this._iaddn(word); + } + } + + this._strip(); + } + + public void Copy(BN dest) + { + dest.words = new int[this.Length]; + for (var i = 0; i < this.Length; i++) + { + dest.words[i] = this.words[i]; + } + dest.Length = this.Length; + dest.Negative = this.Negative; + dest.red = this.red; + } + + internal static void move(BN dest, BN src) + { + dest.words = src.words; + dest.Length = src.Length; + dest.Negative = src.Negative; + dest.red = src.red; + } + + private void _move(BN dest) + { + move(dest, this); + } + + /// + /// Clone number + /// + /// + public BN Clone() + { + var r = new BN(); + this.Copy(r); + return r; + } + + private BN _expand(int size) + { + if (this.Length < size) + Array.Resize(ref this.words, size); + while (this.Length < size) + { + this.words[this.Length++] = 0; + } + return this; + } + + internal BN _strip() + { + while (this.Length > 1 && this.words[this.Length - 1] == 0) + { + this.Length--; + } + Array.Resize(ref this.words, this.Length); + return this._normSign(); + } + + private BN _normSign() + { + // -0 = 0 + if (this.Length == 1 && this.words[0] == 0) + { + this.Negative = 0; + } + return this; + } + + public string Inspect() + { + return (this.red != null ? ""; + } + + private readonly string[] zeros = new string[] { + "", + "0", + "00", + "000", + "0000", + "00000", + "000000", + "0000000", + "00000000", + "000000000", + "0000000000", + "00000000000", + "000000000000", + "0000000000000", + "00000000000000", + "000000000000000", + "0000000000000000", + "00000000000000000", + "000000000000000000", + "0000000000000000000", + "00000000000000000000", + "000000000000000000000", + "0000000000000000000000", + "00000000000000000000000", + "000000000000000000000000", + "0000000000000000000000000" + }; + + private readonly int[] groupSizes = new int[] { + 0, 0, + 25, 16, 12, 11, 10, 9, 8, + 8, 7, 7, 7, 7, 6, 6, + 6, 6, 6, 6, 6, 5, 5, + 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5 + }; + + private readonly int[] groupBases = new int[] { + 0, 0, + 33554432, 43046721, 16777216, 48828125, 60466176, 40353607, 16777216, + 43046721, 10000000, 19487171, 35831808, 62748517, 7529536, 11390625, + 16777216, 24137569, 34012224, 47045881, 64000000, 4084101, 5153632, + 6436343, 7962624, 9765625, 11881376, 14348907, 17210368, 20511149, + 24300000, 28629151, 33554432, 39135393, 45435424, 52521875, 60466176 + }; + + /// + /// Convert to base-string + /// + /// + /// + public override string ToString() + { + return ToString(null); + } + + /// + /// Convert to base-string and pad with zeroes + /// + /// + /// + /// + /// + public string ToString(string? @base, int? padding = null) + { + int baseInt = _baseStringToInt(@base); + return ToString(baseInt, padding); + } + + /// + /// Convert to base-string and pad with zeroes + /// + /// + /// + /// + /// + public string ToString(int @base, int? padding = null) + { + padding = padding ?? 1; + + string @out; + if (@base == 16) + { + @out = ""; + var off = 0; + int carry = 0; + for (var i = 0; i < this.Length; i++) + { + var w = this.words[i]; + var word = (((w << off) | carry) & 0xffffff).ToString(16); + carry = (w >> (24 - off)) & 0xffffff; + off += 2; + if (off >= 26) + { + off -= 26; + i--; + } + if (carry != 0 || i != this.Length - 1) + { + @out = zeros[6 - word.Length] + word + @out; + } + else + { + @out = word + @out; + } + } + if (carry != 0) + { + @out = carry.ToString(16) + @out; + } + while (@out.Length % padding != 0) { + @out = '0' + @out; + } + if (this.Negative != 0) + { + @out = '-' + @out; + } + return @out; + } + + if (@base == (@base | 0) && @base >= 2 && @base <= 36) + { + // var groupSize = Math.floor(BN.wordSize * Math.LN2 / Math.log(@base)); + var groupSize = groupSizes[@base]; + // var group@base = Math.pow(@base, groupSize); + var groupbase = groupBases[@base]; + @out = ""; + var c = this.Clone(); + c.Negative = 0; + while (!c.IsZero()) + { + var r = c.Modrn(groupbase).ToString(@base); + c = c.Idivn(groupbase); + + if (!c.IsZero()) + { + @out = zeros[groupSize - r.Length] + r + @out; + } + else + { + @out = r + @out; + } + } + if (this.IsZero()) + { + @out = '0' + @out; + } + while (@out.Length % padding != 0) { + @out = '0' + @out; + } + if (this.Negative != 0) + { + @out = '-' + @out; + } + return @out; + } + + Functions.Assert(false, "Base should be between 2 and 36"); + return ""; + } + + /// + /// Convert to Number (limited to 53 bits) + /// + /// + /// + public long ToNumber() + { + var ret = (long)this.words[0]; + if (this.Length == 2) + { + ret += (long)this.words[1] * 0x4000000; + } + else if (this.Length == 3 && this.words[2] == 0x01) + { + // NOTE: at this stage it is known that the top bit is set + ret += 0x10000000000000 + ((long)this.words[1] * 0x4000000); + } + else if (this.Length > 2) + { + Functions.Assert(false, "Number can only safely store up to 53 bits"); + } + return (this.Negative != 0) ? -ret : ret; + } + + /// + /// Convert to JSON compatible hex string (alias of ToString(16)) + /// + /// + /// + public string ToJSON() + { + return this.ToString(16, 2); + } + + private byte[] allocate(long size) + { + var array = new byte[size]; + return array; + } + + /// + /// Convert to byte Array, and optionally zero pad to length, throwing if already exceeding + /// + /// + /// + /// + /// + public byte[] ToArray(Endian endian = Endian.BigEndian, int? length = null) + { + this._strip(); + + var byteLength = this.ByteLength(); + var reqLength = length ?? Math.Max(1, byteLength); + + Functions.Assert(byteLength <= reqLength, "byte array longer than desired length"); + Functions.Assert(reqLength > 0, "Requested array length <= 0"); + + var res = allocate(reqLength); + if (endian == Endian.LittleEndian) + { + _toArrayLikeLE(res, byteLength); + } + else + { + _toArrayLikeBE(res, byteLength); + } + return res; + } + + private void _toArrayLikeLE(byte[] res, long byteLength) + { + var position = 0; + long carry = 0; + + for (int i = 0, shift = 0; i < this.Length; i++) + { + var word = (this.words[i] << shift) | carry; + + res[position++] = (byte)(word & 0xff); + if (position < res.Length) + { + res[position++] = (byte)((word >> 8) & 0xff); + } + if (position < res.Length) + { + res[position++] = (byte)((word >> 16) & 0xff); + } + + if (shift == 6) + { + if (position < res.Length) + { + res[position++] = (byte)((word >> 24) & 0xff); + } + carry = 0; + shift = 0; + } + else + { + carry = word >> 24; + shift += 2; + } + } + + if (position < res.Length) + { + res[position++] = (byte)(carry); + + while (position < res.Length) + { + res[position++] = 0; + } + } + } + + private void _toArrayLikeBE(byte[] res, long byteLength) + { + var position = res.Length - 1; + long carry = 0; + + for (int i = 0, shift = 0; i < this.Length; i++) + { + var word = (this.words[i] << shift) | carry; + + res[position--] = (byte)(word & 0xff); + if (position >= 0) + { + res[position--] = (byte)((word >> 8) & 0xff); + } + if (position >= 0) + { + res[position--] = (byte)((word >> 16) & 0xff); + } + + if (shift == 6) + { + if (position >= 0) + { + res[position--] = (byte)((word >> 24) & 0xff); + } + carry = 0; + shift = 0; + } + else + { + carry = word >> 24; + shift += 2; + } + } + + if (position >= 0) + { + res[position--] = (byte)(carry); + + while (position >= 0) + { + res[position--] = 0; + } + } + } + + internal static int _countBits(int w) + { + var t = w; + var r = 0; + if (t >= 0x1000) + { + r += 13; + t >>= 13; + } + if (t >= 0x40) + { + r += 7; + t >>= 7; + } + if (t >= 0x8) + { + r += 4; + t >>= 4; + } + if (t >= 0x02) + { + r += 2; + t >>= 2; + } + return r + t; + } + + private long _zeroBits(int w) + { + // Short-cut + if (w == 0) return 26; + + var t = w; + var r = 0; + if ((t & 0x1fff) == 0) + { + r += 13; + t >>= 13; + } + if ((t & 0x7f) == 0) + { + r += 7; + t >>= 7; + } + if ((t & 0xf) == 0) + { + r += 4; + t >>= 4; + } + if ((t & 0x3) == 0) + { + r += 2; + t >>= 2; + } + if ((t & 0x1) == 0) + { + r++; + } + return r; + } + + /// + /// + /// + /// Number of bits occupied + public int BitLength() + { + var w = this.words[this.Length - 1]; + var hi = BN._countBits(w); + return (this.Length - 1) * 26 + hi; + } + + private byte[] toBitArray(BN num) + { + var w = new byte[num.BitLength()]; + + for (var bit = 0; bit < w.Length; bit++) + { + var off = (bit / 26) | 0; + var wbit = bit % 26; + + w[bit] = (byte)((num.words[off] >> wbit) & 0x01); + } + + return w; + } + + /// + /// + /// + /// Number of less-significant consequent zero bits (example: 1010000 has 4 zero bits) + public long ZeroBits() + { + if (this.IsZero()) return 0; + + long r = 0; + for (var i = 0; i < this.Length; i++) + { + var b = this._zeroBits(this.words[i]); + r += b; + if (b != 26) break; + } + return r; + } + + /// + /// + /// + /// Number of bytes occupied + public long ByteLength() + { + return (long)Math.Ceiling((double)this.BitLength() / 8); + } + + /// + /// Convert to two's complement representation + /// + /// Bit width + /// + /// + public BN ToTwos(int width) + { + if (this.Negative != 0) + { + return this.Abs().Inotn(width).Iaddn(1); + } + return this.Clone(); + } + + /// + /// Convert from two's complement representation + /// + /// Bit width + /// + /// + public BN FromTwos(int width) + { + if (this.Testn(width - 1)) + { + return this.Notn(width).Iaddn(1).Ineg(); + } + return this.Clone(); + } + + /// + /// + /// + /// True if the number is negative + public bool IsNeg() + { + return this.Negative != 0; + } + + /// + /// Negate sign + /// + /// Negative clone of `this` + public BN Neg() + { + return this.Clone().Ineg(); + } + + /// + /// Negate sign + /// + /// + public BN Ineg() + { + if (!this.IsZero()) + { + this.Negative ^= 1; + } + + return this; + } + + /// + /// Or `num` with `this` in-place + /// + /// + /// + public BN Iuor(BN num) + { + if (this.Length < num.Length) + Array.Resize(ref this.words, num.Length); + while (this.Length < num.Length) + { + this.words[this.Length++] = 0; + } + + for (var i = 0; i < num.Length; i++) + { + this.words[i] = this.words[i] | num.words[i]; + } + + return this._strip(); + } + + /// + /// Or `num` with `this` in-place + /// + /// + /// + /// + public BN Ior(BN num) + { + Functions.Assert((this.Negative | num.Negative) == 0); + return this.Iuor(num); + } + + /// + /// Or `num` with `this` + /// + /// + /// + /// + public BN Or(BN num) + { + if (this.Length > num.Length) return this.Clone().Ior(num); + return num.Clone().Ior(this); + } + + /// + /// Or `num` with `this` + /// + /// + /// + public BN Uor(BN num) + { + if (this.Length > num.Length) return this.Clone().Iuor(num); + return num.Clone().Iuor(this); + } + + /// + /// And `num` with `this` in-place + /// + /// + /// + public BN Iuand(BN num) + { + // b = min-length(num, this) + BN b; + if (this.Length > num.Length) + { + b = num; + } + else + { + b = this; + } + + for (var i = 0; i < b.Length; i++) + { + this.words[i] = this.words[i] & num.words[i]; + } + + this.Length = b.Length; + + return this._strip(); + } + + /// + /// And `num` with `this` in-place + /// + /// + /// + /// + public BN Iand(BN num) + { + Functions.Assert((this.Negative | num.Negative) == 0); + return this.Iuand(num); + } + + /// + /// And `num` with `this` + /// + /// + /// + /// + public BN And(BN num) + { + if (this.Length > num.Length) return this.Clone().Iand(num); + return num.Clone().Iand(this); + } + + /// + /// And `num` with `this` + /// + /// + /// + public BN Uand(BN num) + { + if (this.Length > num.Length) return this.Clone().Iuand(num); + return num.Clone().Iuand(this); + } + + /// + /// Xor `num` with `this` in-place + /// + /// + /// + public BN Iuxor(BN num) + { + // a.length > b.length + BN a; + BN b; + if (this.Length > num.Length) + { + a = this; + b = num; + } + else + { + a = num; + b = this; + } + + int i; + for (i = 0; i < b.Length; i++) + { + this.words[i] = a.words[i] ^ b.words[i]; + } + + if (this != a) + { + Array.Resize(ref this.words, a.Length); + for (; i < a.Length; i++) + { + this.words[i] = a.words[i]; + } + } + + this.Length = a.Length; + + return this._strip(); + } + + /// + /// Xor `num` with `this` in-place + /// + /// + /// + /// + public BN Ixor(BN num) + { + Functions.Assert((this.Negative | num.Negative) == 0); + return this.Iuxor(num); + } + + /// + /// Xor `num` with `this` + /// + /// + /// + /// + public BN Xor(BN num) + { + if (this.Length > num.Length) return this.Clone().Ixor(num); + return num.Clone().Ixor(this); + } + + /// + /// Xor `num` with `this` + /// + /// + /// + public BN Uxor(BN num) + { + if (this.Length > num.Length) return this.Clone().Iuxor(num); + return num.Clone().Iuxor(this); + } + + /// + /// Not `this` with `width` bitwidth + /// + /// + /// + /// + public BN Inotn(int width) + { + Functions.Assert(width >= 0); + + var bytesNeeded = (int)Math.Ceiling((double)width / 26) | 0; + var bitsLeft = width % 26; + + // Extend the buffer with leading zeroes + this._expand(bytesNeeded); + + if (bitsLeft > 0) + { + bytesNeeded--; + } + + // Handle complete words + int i; + for (i = 0; i < bytesNeeded; i++) + { + this.words[i] = ~this.words[i] & 0x3ffffff; + } + + // Handle the residue + if (bitsLeft > 0) + { + this.words[i] = ~this.words[i] & (0x3ffffff >> (26 - bitsLeft)); + } + + // And remove leading zeroes + return this._strip(); + } + + /// + /// Not `this` with `width` bitwidth + /// + /// + /// + /// + public BN Notn(int width) + { + return this.Clone().Inotn(width); + } + + /// + /// Set `bit` of `this` + /// + /// + /// + /// + /// + public BN Setn(int bit, byte? val) + { + Functions.Assert(bit >= 0); + + var off = (bit / 26) | 0; + var wbit = bit % 26; + + this._expand(off + 1); + + if (val != null && val != 0) + { + this.words[off] = this.words[off] | (1 << wbit); + } + else + { + this.words[off] = this.words[off] & ~(1 << wbit); + } + + return this._strip(); + } + + /// + /// Add `num` to `this` in-place + /// + /// + /// + public BN Iadd(BN num) + { + BN r0; + + // negative + positive + if (this.Negative != 0 && num.Negative == 0) + { + this.Negative = 0; + r0 = this.Isub(num); + this.Negative ^= 1; + return this._normSign(); + + // positive + negative + } + else if (this.Negative == 0 && num.Negative != 0) + { + num.Negative = 0; + r0 = this.Isub(num); + num.Negative = 1; + return r0._normSign(); + } + + // a.length > b.length + BN a, b; + if (this.Length > num.Length) + { + a = this; + b = num; + } + else + { + a = num; + b = this; + } + + int carry = 0; + int i; + int r; + for (i = 0; i < b.Length; i++) + { + r = (a.words[i] | 0) + (b.words[i] | 0) + carry; + this.words[i] = r & 0x3ffffff; + carry = r >> 26; + } + Array.Resize(ref this.words, a.Length); + for (; carry != 0 && i < a.Length; i++) + { + r = (a.words[i] | 0) + carry; + this.words[i] = r & 0x3ffffff; + carry = r >> 26; + } + + this.Length = a.Length; + if (carry != 0) + { + Array.Resize(ref this.words, this.Length + 1); + this.words[this.Length] = carry; + this.Length++; + // Copy the rest of the words + } + else if (a != this) + { + for (; i < a.Length; i++) + { + this.words[i] = a.words[i]; + } + } + + return this; + } + + /// + /// Add `num` to `this` + /// + /// + /// + public BN Add(BN num) + { + BN res; + if (num.Negative != 0 && this.Negative == 0) + { + num.Negative = 0; + res = this.Sub(num); + num.Negative ^= 1; + return res; + } + else if (num.Negative == 0 && this.Negative != 0) + { + this.Negative = 0; + res = num.Sub(this); + this.Negative = 1; + return res; + } + + if (this.Length > num.Length) return this.Clone().Iadd(num); + + return num.Clone().Iadd(this); + } + + /// + /// Subtract `num` from `this` in-place + /// + /// + /// + public BN Isub(BN num) + { + // this - (-num) = this + num + if (num.Negative != 0) + { + num.Negative = 0; + var r0 = this.Iadd(num); + num.Negative = 1; + return r0._normSign(); + + // -this - num = -(this + num) + } + else if (this.Negative != 0) + { + this.Negative = 0; + this.Iadd(num); + this.Negative = 1; + return this._normSign(); + } + + // At this point both numbers are positive + var cmp = this.Cmp(num); + + // Optimization - zeroify + if (cmp == 0) + { + this.Negative = 0; + this.Length = 1; + this.words[0] = 0; + return this; + } + + // a > b + BN a, b; + if (cmp > 0) + { + a = this; + b = num; + } + else + { + a = num; + b = this; + } + + int carry = 0; + int r; + int i; + for (i = 0; i < b.Length; i++) + { + r = (a.words[i] | 0) - (b.words[i] | 0) + carry; + carry = r >> 26; + this.words[i] = r & 0x3ffffff; + } + Array.Resize(ref this.words, a.Length); + for (; carry != 0 && i < a.Length; i++) + { + r = (a.words[i] | 0) + carry; + carry = r >> 26; + this.words[i] = r & 0x3ffffff; + } + + // Copy rest of the words + if (carry == 0 && i < a.Length && a != this) + { + for (; i < a.Length; i++) + { + this.words[i] = a.words[i]; + } + } + + this.Length = Math.Max(this.Length, i); + + if (a != this) + { + this.Negative = 1; + } + + return this._strip(); + } + + /// + /// Subtract `num` from `this` + /// + /// + /// + public BN Sub(BN num) + { + return this.Clone().Isub(num); + } + + private BN smallMulTo(BN self, BN num, BN @out) + { + @out.Negative = num.Negative ^ self.Negative; + var len = (self.Length + num.Length) | 0; + @out.Length = len; + Array.Resize(ref @out.words, @out.Length); + len = (len - 1) | 0; + + // Peel one iteration (compiler can't do it, because of code complexity) + long a = self.words[0] | 0; + long b = num.words[0] | 0; + long r = a * b; + + var lo = (int)(r & 0x3ffffff); + var carry = (r / 0x4000000) | 0; + @out.words[0] = lo; + + int k; + for (k = 1; k < len; k++) + { + // Sum all words with the same `i + j = k` and accumulate `ncarry`, + // note that ncarry could be >= 0x3ffffff + var ncarry = carry >> 26; + var rword = carry & 0x3ffffff; + var maxJ = Math.Min(k, num.Length - 1); + for (var j = Math.Max(0, k - self.Length + 1); j <= maxJ; j++) + { + var i = (k - j) | 0; + a = self.words[i] | 0; + b = num.words[j] | 0; + r = a * b + rword; + ncarry += (r / 0x4000000) | 0; + rword = r & 0x3ffffff; + } + @out.words[k] = (int)(rword | 0); + carry = ncarry | 0; + } + if (carry != 0) + { + @out.Length++; + Array.Resize(ref @out.words, @out.Length); + + @out.words[k] = (int)(carry | 0); + } + else + { + //? + @out.Length--; + } + + return @out._strip(); + } + + // TODO(indutny): it may be reasonable to omit it for users who don't need + // to work with 256-bit numbers, otherwise it gives 20% improvement for 256-bit + // multiplication (like elliptic secp256k1). + private BN comb10MulTo(BN self, BN num, BN @out) + { + var a = self.words; + var b = num.words; + var o = @out.words; + var c = 0; + int lo; + int mid; + int hi; + var a0 = a[0] | 0; + var al0 = a0 & 0x1fff; + var ah0 = a0 >> 13; + var a1 = a[1] | 0; + var al1 = a1 & 0x1fff; + var ah1 = a1 >> 13; + var a2 = a[2] | 0; + var al2 = a2 & 0x1fff; + var ah2 = a2 >> 13; + var a3 = a[3] | 0; + var al3 = a3 & 0x1fff; + var ah3 = a3 >> 13; + var a4 = a[4] | 0; + var al4 = a4 & 0x1fff; + var ah4 = a4 >> 13; + var a5 = a[5] | 0; + var al5 = a5 & 0x1fff; + var ah5 = a5 >> 13; + var a6 = a[6] | 0; + var al6 = a6 & 0x1fff; + var ah6 = a6 >> 13; + var a7 = a[7] | 0; + var al7 = a7 & 0x1fff; + var ah7 = a7 >> 13; + var a8 = a[8] | 0; + var al8 = a8 & 0x1fff; + var ah8 = a8 >> 13; + var a9 = a[9] | 0; + var al9 = a9 & 0x1fff; + var ah9 = a9 >> 13; + var b0 = b[0] | 0; + var bl0 = b0 & 0x1fff; + var bh0 = b0 >> 13; + var b1 = b[1] | 0; + var bl1 = b1 & 0x1fff; + var bh1 = b1 >> 13; + var b2 = b[2] | 0; + var bl2 = b2 & 0x1fff; + var bh2 = b2 >> 13; + var b3 = b[3] | 0; + var bl3 = b3 & 0x1fff; + var bh3 = b3 >> 13; + var b4 = b[4] | 0; + var bl4 = b4 & 0x1fff; + var bh4 = b4 >> 13; + var b5 = b[5] | 0; + var bl5 = b5 & 0x1fff; + var bh5 = b5 >> 13; + var b6 = b[6] | 0; + var bl6 = b6 & 0x1fff; + var bh6 = b6 >> 13; + var b7 = b[7] | 0; + var bl7 = b7 & 0x1fff; + var bh7 = b7 >> 13; + var b8 = b[8] | 0; + var bl8 = b8 & 0x1fff; + var bh8 = b8 >> 13; + var b9 = b[9] | 0; + var bl9 = b9 & 0x1fff; + var bh9 = b9 >> 13; + + @out.Negative = self.Negative ^ num.Negative; + @out.Length = 19; + /* k = 0 */ + lo = (al0 * bl0); + mid = (al0 * bh0); + mid = (mid + (ah0 * bl0)) | 0; + hi = (ah0 * bh0); + var w0 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w0 >> 26)) | 0; + w0 &= 0x3ffffff; + /* k = 1 */ + lo = (al1 * bl0); + mid = (al1 * bh0); + mid = (mid + (ah1 * bl0)) | 0; + hi = (ah1 * bh0); + lo = (lo + (al0 * bl1)) | 0; + mid = (mid + (al0 * bh1)) | 0; + mid = (mid + (ah0 * bl1)) | 0; + hi = (hi + (ah0 * bh1)) | 0; + var w1 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w1 >> 26)) | 0; + w1 &= 0x3ffffff; + /* k = 2 */ + lo = (al2 * bl0); + mid = (al2 * bh0); + mid = (mid + (ah2 * bl0)) | 0; + hi = (ah2 * bh0); + lo = (lo + (al1 * bl1)) | 0; + mid = (mid + (al1 * bh1)) | 0; + mid = (mid + (ah1 * bl1)) | 0; + hi = (hi + (ah1 * bh1)) | 0; + lo = (lo + (al0 * bl2)) | 0; + mid = (mid + (al0 * bh2)) | 0; + mid = (mid + (ah0 * bl2)) | 0; + hi = (hi + (ah0 * bh2)) | 0; + var w2 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w2 >> 26)) | 0; + w2 &= 0x3ffffff; + /* k = 3 */ + lo = (al3 * bl0); + mid = (al3 * bh0); + mid = (mid + (ah3 * bl0)) | 0; + hi = (ah3 * bh0); + lo = (lo + (al2 * bl1)) | 0; + mid = (mid + (al2 * bh1)) | 0; + mid = (mid + (ah2 * bl1)) | 0; + hi = (hi + (ah2 * bh1)) | 0; + lo = (lo + (al1 * bl2)) | 0; + mid = (mid + (al1 * bh2)) | 0; + mid = (mid + (ah1 * bl2)) | 0; + hi = (hi + (ah1 * bh2)) | 0; + lo = (lo + (al0 * bl3)) | 0; + mid = (mid + (al0 * bh3)) | 0; + mid = (mid + (ah0 * bl3)) | 0; + hi = (hi + (ah0 * bh3)) | 0; + var w3 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w3 >> 26)) | 0; + w3 &= 0x3ffffff; + /* k = 4 */ + lo = (al4 * bl0); + mid = (al4 * bh0); + mid = (mid + (ah4 * bl0)) | 0; + hi = (ah4 * bh0); + lo = (lo + (al3 * bl1)) | 0; + mid = (mid + (al3 * bh1)) | 0; + mid = (mid + (ah3 * bl1)) | 0; + hi = (hi + (ah3 * bh1)) | 0; + lo = (lo + (al2 * bl2)) | 0; + mid = (mid + (al2 * bh2)) | 0; + mid = (mid + (ah2 * bl2)) | 0; + hi = (hi + (ah2 * bh2)) | 0; + lo = (lo + (al1 * bl3)) | 0; + mid = (mid + (al1 * bh3)) | 0; + mid = (mid + (ah1 * bl3)) | 0; + hi = (hi + (ah1 * bh3)) | 0; + lo = (lo + (al0 * bl4)) | 0; + mid = (mid + (al0 * bh4)) | 0; + mid = (mid + (ah0 * bl4)) | 0; + hi = (hi + (ah0 * bh4)) | 0; + var w4 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w4 >> 26)) | 0; + w4 &= 0x3ffffff; + /* k = 5 */ + lo = (al5 * bl0); + mid = (al5 * bh0); + mid = (mid + (ah5 * bl0)) | 0; + hi = (ah5 * bh0); + lo = (lo + (al4 * bl1)) | 0; + mid = (mid + (al4 * bh1)) | 0; + mid = (mid + (ah4 * bl1)) | 0; + hi = (hi + (ah4 * bh1)) | 0; + lo = (lo + (al3 * bl2)) | 0; + mid = (mid + (al3 * bh2)) | 0; + mid = (mid + (ah3 * bl2)) | 0; + hi = (hi + (ah3 * bh2)) | 0; + lo = (lo + (al2 * bl3)) | 0; + mid = (mid + (al2 * bh3)) | 0; + mid = (mid + (ah2 * bl3)) | 0; + hi = (hi + (ah2 * bh3)) | 0; + lo = (lo + (al1 * bl4)) | 0; + mid = (mid + (al1 * bh4)) | 0; + mid = (mid + (ah1 * bl4)) | 0; + hi = (hi + (ah1 * bh4)) | 0; + lo = (lo + (al0 * bl5)) | 0; + mid = (mid + (al0 * bh5)) | 0; + mid = (mid + (ah0 * bl5)) | 0; + hi = (hi + (ah0 * bh5)) | 0; + var w5 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w5 >> 26)) | 0; + w5 &= 0x3ffffff; + /* k = 6 */ + lo = (al6 * bl0); + mid = (al6 * bh0); + mid = (mid + (ah6 * bl0)) | 0; + hi = (ah6 * bh0); + lo = (lo + (al5 * bl1)) | 0; + mid = (mid + (al5 * bh1)) | 0; + mid = (mid + (ah5 * bl1)) | 0; + hi = (hi + (ah5 * bh1)) | 0; + lo = (lo + (al4 * bl2)) | 0; + mid = (mid + (al4 * bh2)) | 0; + mid = (mid + (ah4 * bl2)) | 0; + hi = (hi + (ah4 * bh2)) | 0; + lo = (lo + (al3 * bl3)) | 0; + mid = (mid + (al3 * bh3)) | 0; + mid = (mid + (ah3 * bl3)) | 0; + hi = (hi + (ah3 * bh3)) | 0; + lo = (lo + (al2 * bl4)) | 0; + mid = (mid + (al2 * bh4)) | 0; + mid = (mid + (ah2 * bl4)) | 0; + hi = (hi + (ah2 * bh4)) | 0; + lo = (lo + (al1 * bl5)) | 0; + mid = (mid + (al1 * bh5)) | 0; + mid = (mid + (ah1 * bl5)) | 0; + hi = (hi + (ah1 * bh5)) | 0; + lo = (lo + (al0 * bl6)) | 0; + mid = (mid + (al0 * bh6)) | 0; + mid = (mid + (ah0 * bl6)) | 0; + hi = (hi + (ah0 * bh6)) | 0; + var w6 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w6 >> 26)) | 0; + w6 &= 0x3ffffff; + /* k = 7 */ + lo = (al7 * bl0); + mid = (al7 * bh0); + mid = (mid + (ah7 * bl0)) | 0; + hi = (ah7 * bh0); + lo = (lo + (al6 * bl1)) | 0; + mid = (mid + (al6 * bh1)) | 0; + mid = (mid + (ah6 * bl1)) | 0; + hi = (hi + (ah6 * bh1)) | 0; + lo = (lo + (al5 * bl2)) | 0; + mid = (mid + (al5 * bh2)) | 0; + mid = (mid + (ah5 * bl2)) | 0; + hi = (hi + (ah5 * bh2)) | 0; + lo = (lo + (al4 * bl3)) | 0; + mid = (mid + (al4 * bh3)) | 0; + mid = (mid + (ah4 * bl3)) | 0; + hi = (hi + (ah4 * bh3)) | 0; + lo = (lo + (al3 * bl4)) | 0; + mid = (mid + (al3 * bh4)) | 0; + mid = (mid + (ah3 * bl4)) | 0; + hi = (hi + (ah3 * bh4)) | 0; + lo = (lo + (al2 * bl5)) | 0; + mid = (mid + (al2 * bh5)) | 0; + mid = (mid + (ah2 * bl5)) | 0; + hi = (hi + (ah2 * bh5)) | 0; + lo = (lo + (al1 * bl6)) | 0; + mid = (mid + (al1 * bh6)) | 0; + mid = (mid + (ah1 * bl6)) | 0; + hi = (hi + (ah1 * bh6)) | 0; + lo = (lo + (al0 * bl7)) | 0; + mid = (mid + (al0 * bh7)) | 0; + mid = (mid + (ah0 * bl7)) | 0; + hi = (hi + (ah0 * bh7)) | 0; + var w7 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w7 >> 26)) | 0; + w7 &= 0x3ffffff; + /* k = 8 */ + lo = (al8 * bl0); + mid = (al8 * bh0); + mid = (mid + (ah8 * bl0)) | 0; + hi = (ah8 * bh0); + lo = (lo + (al7 * bl1)) | 0; + mid = (mid + (al7 * bh1)) | 0; + mid = (mid + (ah7 * bl1)) | 0; + hi = (hi + (ah7 * bh1)) | 0; + lo = (lo + (al6 * bl2)) | 0; + mid = (mid + (al6 * bh2)) | 0; + mid = (mid + (ah6 * bl2)) | 0; + hi = (hi + (ah6 * bh2)) | 0; + lo = (lo + (al5 * bl3)) | 0; + mid = (mid + (al5 * bh3)) | 0; + mid = (mid + (ah5 * bl3)) | 0; + hi = (hi + (ah5 * bh3)) | 0; + lo = (lo + (al4 * bl4)) | 0; + mid = (mid + (al4 * bh4)) | 0; + mid = (mid + (ah4 * bl4)) | 0; + hi = (hi + (ah4 * bh4)) | 0; + lo = (lo + (al3 * bl5)) | 0; + mid = (mid + (al3 * bh5)) | 0; + mid = (mid + (ah3 * bl5)) | 0; + hi = (hi + (ah3 * bh5)) | 0; + lo = (lo + (al2 * bl6)) | 0; + mid = (mid + (al2 * bh6)) | 0; + mid = (mid + (ah2 * bl6)) | 0; + hi = (hi + (ah2 * bh6)) | 0; + lo = (lo + (al1 * bl7)) | 0; + mid = (mid + (al1 * bh7)) | 0; + mid = (mid + (ah1 * bl7)) | 0; + hi = (hi + (ah1 * bh7)) | 0; + lo = (lo + (al0 * bl8)) | 0; + mid = (mid + (al0 * bh8)) | 0; + mid = (mid + (ah0 * bl8)) | 0; + hi = (hi + (ah0 * bh8)) | 0; + var w8 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w8 >> 26)) | 0; + w8 &= 0x3ffffff; + /* k = 9 */ + lo = (al9 * bl0); + mid = (al9 * bh0); + mid = (mid + (ah9 * bl0)) | 0; + hi = (ah9 * bh0); + lo = (lo + (al8 * bl1)) | 0; + mid = (mid + (al8 * bh1)) | 0; + mid = (mid + (ah8 * bl1)) | 0; + hi = (hi + (ah8 * bh1)) | 0; + lo = (lo + (al7 * bl2)) | 0; + mid = (mid + (al7 * bh2)) | 0; + mid = (mid + (ah7 * bl2)) | 0; + hi = (hi + (ah7 * bh2)) | 0; + lo = (lo + (al6 * bl3)) | 0; + mid = (mid + (al6 * bh3)) | 0; + mid = (mid + (ah6 * bl3)) | 0; + hi = (hi + (ah6 * bh3)) | 0; + lo = (lo + (al5 * bl4)) | 0; + mid = (mid + (al5 * bh4)) | 0; + mid = (mid + (ah5 * bl4)) | 0; + hi = (hi + (ah5 * bh4)) | 0; + lo = (lo + (al4 * bl5)) | 0; + mid = (mid + (al4 * bh5)) | 0; + mid = (mid + (ah4 * bl5)) | 0; + hi = (hi + (ah4 * bh5)) | 0; + lo = (lo + (al3 * bl6)) | 0; + mid = (mid + (al3 * bh6)) | 0; + mid = (mid + (ah3 * bl6)) | 0; + hi = (hi + (ah3 * bh6)) | 0; + lo = (lo + (al2 * bl7)) | 0; + mid = (mid + (al2 * bh7)) | 0; + mid = (mid + (ah2 * bl7)) | 0; + hi = (hi + (ah2 * bh7)) | 0; + lo = (lo + (al1 * bl8)) | 0; + mid = (mid + (al1 * bh8)) | 0; + mid = (mid + (ah1 * bl8)) | 0; + hi = (hi + (ah1 * bh8)) | 0; + lo = (lo + (al0 * bl9)) | 0; + mid = (mid + (al0 * bh9)) | 0; + mid = (mid + (ah0 * bl9)) | 0; + hi = (hi + (ah0 * bh9)) | 0; + var w9 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w9 >> 26)) | 0; + w9 &= 0x3ffffff; + /* k = 10 */ + lo = (al9 * bl1); + mid = (al9 * bh1); + mid = (mid + (ah9 * bl1)) | 0; + hi = (ah9 * bh1); + lo = (lo + (al8 * bl2)) | 0; + mid = (mid + (al8 * bh2)) | 0; + mid = (mid + (ah8 * bl2)) | 0; + hi = (hi + (ah8 * bh2)) | 0; + lo = (lo + (al7 * bl3)) | 0; + mid = (mid + (al7 * bh3)) | 0; + mid = (mid + (ah7 * bl3)) | 0; + hi = (hi + (ah7 * bh3)) | 0; + lo = (lo + (al6 * bl4)) | 0; + mid = (mid + (al6 * bh4)) | 0; + mid = (mid + (ah6 * bl4)) | 0; + hi = (hi + (ah6 * bh4)) | 0; + lo = (lo + (al5 * bl5)) | 0; + mid = (mid + (al5 * bh5)) | 0; + mid = (mid + (ah5 * bl5)) | 0; + hi = (hi + (ah5 * bh5)) | 0; + lo = (lo + (al4 * bl6)) | 0; + mid = (mid + (al4 * bh6)) | 0; + mid = (mid + (ah4 * bl6)) | 0; + hi = (hi + (ah4 * bh6)) | 0; + lo = (lo + (al3 * bl7)) | 0; + mid = (mid + (al3 * bh7)) | 0; + mid = (mid + (ah3 * bl7)) | 0; + hi = (hi + (ah3 * bh7)) | 0; + lo = (lo + (al2 * bl8)) | 0; + mid = (mid + (al2 * bh8)) | 0; + mid = (mid + (ah2 * bl8)) | 0; + hi = (hi + (ah2 * bh8)) | 0; + lo = (lo + (al1 * bl9)) | 0; + mid = (mid + (al1 * bh9)) | 0; + mid = (mid + (ah1 * bl9)) | 0; + hi = (hi + (ah1 * bh9)) | 0; + var w10 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w10 >> 26)) | 0; + w10 &= 0x3ffffff; + /* k = 11 */ + lo = (al9 * bl2); + mid = (al9 * bh2); + mid = (mid + (ah9 * bl2)) | 0; + hi = (ah9 * bh2); + lo = (lo + (al8 * bl3)) | 0; + mid = (mid + (al8 * bh3)) | 0; + mid = (mid + (ah8 * bl3)) | 0; + hi = (hi + (ah8 * bh3)) | 0; + lo = (lo + (al7 * bl4)) | 0; + mid = (mid + (al7 * bh4)) | 0; + mid = (mid + (ah7 * bl4)) | 0; + hi = (hi + (ah7 * bh4)) | 0; + lo = (lo + (al6 * bl5)) | 0; + mid = (mid + (al6 * bh5)) | 0; + mid = (mid + (ah6 * bl5)) | 0; + hi = (hi + (ah6 * bh5)) | 0; + lo = (lo + (al5 * bl6)) | 0; + mid = (mid + (al5 * bh6)) | 0; + mid = (mid + (ah5 * bl6)) | 0; + hi = (hi + (ah5 * bh6)) | 0; + lo = (lo + (al4 * bl7)) | 0; + mid = (mid + (al4 * bh7)) | 0; + mid = (mid + (ah4 * bl7)) | 0; + hi = (hi + (ah4 * bh7)) | 0; + lo = (lo + (al3 * bl8)) | 0; + mid = (mid + (al3 * bh8)) | 0; + mid = (mid + (ah3 * bl8)) | 0; + hi = (hi + (ah3 * bh8)) | 0; + lo = (lo + (al2 * bl9)) | 0; + mid = (mid + (al2 * bh9)) | 0; + mid = (mid + (ah2 * bl9)) | 0; + hi = (hi + (ah2 * bh9)) | 0; + var w11 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w11 >> 26)) | 0; + w11 &= 0x3ffffff; + /* k = 12 */ + lo = (al9 * bl3); + mid = (al9 * bh3); + mid = (mid + (ah9 * bl3)) | 0; + hi = (ah9 * bh3); + lo = (lo + (al8 * bl4)) | 0; + mid = (mid + (al8 * bh4)) | 0; + mid = (mid + (ah8 * bl4)) | 0; + hi = (hi + (ah8 * bh4)) | 0; + lo = (lo + (al7 * bl5)) | 0; + mid = (mid + (al7 * bh5)) | 0; + mid = (mid + (ah7 * bl5)) | 0; + hi = (hi + (ah7 * bh5)) | 0; + lo = (lo + (al6 * bl6)) | 0; + mid = (mid + (al6 * bh6)) | 0; + mid = (mid + (ah6 * bl6)) | 0; + hi = (hi + (ah6 * bh6)) | 0; + lo = (lo + (al5 * bl7)) | 0; + mid = (mid + (al5 * bh7)) | 0; + mid = (mid + (ah5 * bl7)) | 0; + hi = (hi + (ah5 * bh7)) | 0; + lo = (lo + (al4 * bl8)) | 0; + mid = (mid + (al4 * bh8)) | 0; + mid = (mid + (ah4 * bl8)) | 0; + hi = (hi + (ah4 * bh8)) | 0; + lo = (lo + (al3 * bl9)) | 0; + mid = (mid + (al3 * bh9)) | 0; + mid = (mid + (ah3 * bl9)) | 0; + hi = (hi + (ah3 * bh9)) | 0; + var w12 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w12 >> 26)) | 0; + w12 &= 0x3ffffff; + /* k = 13 */ + lo = (al9 * bl4); + mid = (al9 * bh4); + mid = (mid + (ah9 * bl4)) | 0; + hi = (ah9 * bh4); + lo = (lo + (al8 * bl5)) | 0; + mid = (mid + (al8 * bh5)) | 0; + mid = (mid + (ah8 * bl5)) | 0; + hi = (hi + (ah8 * bh5)) | 0; + lo = (lo + (al7 * bl6)) | 0; + mid = (mid + (al7 * bh6)) | 0; + mid = (mid + (ah7 * bl6)) | 0; + hi = (hi + (ah7 * bh6)) | 0; + lo = (lo + (al6 * bl7)) | 0; + mid = (mid + (al6 * bh7)) | 0; + mid = (mid + (ah6 * bl7)) | 0; + hi = (hi + (ah6 * bh7)) | 0; + lo = (lo + (al5 * bl8)) | 0; + mid = (mid + (al5 * bh8)) | 0; + mid = (mid + (ah5 * bl8)) | 0; + hi = (hi + (ah5 * bh8)) | 0; + lo = (lo + (al4 * bl9)) | 0; + mid = (mid + (al4 * bh9)) | 0; + mid = (mid + (ah4 * bl9)) | 0; + hi = (hi + (ah4 * bh9)) | 0; + var w13 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w13 >> 26)) | 0; + w13 &= 0x3ffffff; + /* k = 14 */ + lo = (al9 * bl5); + mid = (al9 * bh5); + mid = (mid + (ah9 * bl5)) | 0; + hi = (ah9 * bh5); + lo = (lo + (al8 * bl6)) | 0; + mid = (mid + (al8 * bh6)) | 0; + mid = (mid + (ah8 * bl6)) | 0; + hi = (hi + (ah8 * bh6)) | 0; + lo = (lo + (al7 * bl7)) | 0; + mid = (mid + (al7 * bh7)) | 0; + mid = (mid + (ah7 * bl7)) | 0; + hi = (hi + (ah7 * bh7)) | 0; + lo = (lo + (al6 * bl8)) | 0; + mid = (mid + (al6 * bh8)) | 0; + mid = (mid + (ah6 * bl8)) | 0; + hi = (hi + (ah6 * bh8)) | 0; + lo = (lo + (al5 * bl9)) | 0; + mid = (mid + (al5 * bh9)) | 0; + mid = (mid + (ah5 * bl9)) | 0; + hi = (hi + (ah5 * bh9)) | 0; + var w14 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w14 >> 26)) | 0; + w14 &= 0x3ffffff; + /* k = 15 */ + lo = (al9 * bl6); + mid = (al9 * bh6); + mid = (mid + (ah9 * bl6)) | 0; + hi = (ah9 * bh6); + lo = (lo + (al8 * bl7)) | 0; + mid = (mid + (al8 * bh7)) | 0; + mid = (mid + (ah8 * bl7)) | 0; + hi = (hi + (ah8 * bh7)) | 0; + lo = (lo + (al7 * bl8)) | 0; + mid = (mid + (al7 * bh8)) | 0; + mid = (mid + (ah7 * bl8)) | 0; + hi = (hi + (ah7 * bh8)) | 0; + lo = (lo + (al6 * bl9)) | 0; + mid = (mid + (al6 * bh9)) | 0; + mid = (mid + (ah6 * bl9)) | 0; + hi = (hi + (ah6 * bh9)) | 0; + var w15 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w15 >> 26)) | 0; + w15 &= 0x3ffffff; + /* k = 16 */ + lo = (al9 * bl7); + mid = (al9 * bh7); + mid = (mid + (ah9 * bl7)) | 0; + hi = (ah9 * bh7); + lo = (lo + (al8 * bl8)) | 0; + mid = (mid + (al8 * bh8)) | 0; + mid = (mid + (ah8 * bl8)) | 0; + hi = (hi + (ah8 * bh8)) | 0; + lo = (lo + (al7 * bl9)) | 0; + mid = (mid + (al7 * bh9)) | 0; + mid = (mid + (ah7 * bl9)) | 0; + hi = (hi + (ah7 * bh9)) | 0; + var w16 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w16 >> 26)) | 0; + w16 &= 0x3ffffff; + /* k = 17 */ + lo = (al9 * bl8); + mid = (al9 * bh8); + mid = (mid + (ah9 * bl8)) | 0; + hi = (ah9 * bh8); + lo = (lo + (al8 * bl9)) | 0; + mid = (mid + (al8 * bh9)) | 0; + mid = (mid + (ah8 * bl9)) | 0; + hi = (hi + (ah8 * bh9)) | 0; + var w17 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w17 >> 26)) | 0; + w17 &= 0x3ffffff; + /* k = 18 */ + lo = (al9 * bl9); + mid = (al9 * bh9); + mid = (mid + (ah9 * bl9)) | 0; + hi = (ah9 * bh9); + var w18 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0; + c = (((hi + (mid >> 13)) | 0) + (w18 >> 26)) | 0; + w18 &= 0x3ffffff; + o[0] = w0; + o[1] = w1; + o[2] = w2; + o[3] = w3; + o[4] = w4; + o[5] = w5; + o[6] = w6; + o[7] = w7; + o[8] = w8; + o[9] = w9; + o[10] = w10; + o[11] = w11; + o[12] = w12; + o[13] = w13; + o[14] = w14; + o[15] = w15; + o[16] = w16; + o[17] = w17; + o[18] = w18; + if (c != 0) + { + o[19] = c; + @out.Length++; + } + return @out; + } + + private BN bigMulTo(BN self, BN num, BN @out) + { + @out.Negative = num.Negative ^ self.Negative; + @out.Length = self.Length + num.Length; + + Array.Resize(ref @out.words, @out.Length); + + long carry = 0; + long hncarry = 0; + int k; + for (k = 0; k < @out.Length - 1; k++) { + // Sum all words with the same `i + j = k` and accumulate `ncarry`, + // note that ncarry could be >= 0x3ffffff + var ncarry = hncarry; + hncarry = 0; + int rword = (int)(carry & 0x3ffffff); + var maxJ = Math.Min(k, num.Length - 1); + for (var j = Math.Max(0, k - self.Length + 1); j <= maxJ; j++) + { + var i = k - j; + long a = self.words[i] | 0; + long b = num.words[j] | 0; + long r = a * b; + + uint lo = (uint)(r & 0x3ffffff); + ncarry = (ncarry + ((r / 0x4000000) | 0)) | 0; + lo = (uint)((lo + rword) | 0); + rword = (int)(lo & 0x3ffffff); + ncarry = (ncarry + (lo >> 26)) | 0; + + hncarry += ncarry >> 26; + ncarry &= 0x3ffffff; + } + @out.words[k] = rword; + carry = ncarry; + ncarry = hncarry; + } + if (carry != 0) + { + @out.words[k] = (int)carry; + } + else + { + @out.Length--; + } + + return @out._strip(); + } + + private BN jumboMulTo(BN self, BN num, BN @out) + { + // Temporary disable, see https://github.com/indutny/bn.js/issues/211 + // var fftm = new FFTM(); + // return fftm.mulp(self, num, out); + return bigMulTo(self, num, @out); + } + + private BN mulTo(BN num, BN @out) + { + BN res; + var len = this.Length + num.Length; + if (this.Length == 10 && num.Length == 10) + { + res = comb10MulTo(this, num, @out); + } + else if (len < 63) + { + res = smallMulTo(this, num, @out); + } + else if (len < 1024) + { + res = bigMulTo(this, num, @out); + } + else + { + res = jumboMulTo(this, num, @out); + } + + return res; + } + + /// + /// Multiply `this` by `num` + /// + /// + /// + public BN Mul(BN num) + { + var @out = new BN(); + @out.words = new int[this.Length + num.Length]; + return this.mulTo(num, @out); + } + + /// + /// Multiply employing FFT + /// + /// + /// + public BN Mulf(BN num) + { + var @out = new BN(); + @out.words = new int[this.Length + num.Length]; + return jumboMulTo(this, num, @out); + } + + /// + /// In-place Multiplication + /// + /// + /// + public BN Imul(BN num) + { + return this.Clone().mulTo(num, this); + } + + /// + /// In-place Multiplication + /// + /// + /// + /// + public BN Imuln(long num) + { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + Functions.Assert(num < 0x4000000); + + // Carry + long carry = 0; + int i; + for (i = 0; i < this.Length; i++) + { + var w = (this.words[i] | 0) * num; + var lo = (w & 0x3ffffff) + (carry & 0x3ffffff); + carry >>= 26; + carry += (w / 0x4000000) | 0; + // NOTE: lo is 27bit maximum + carry += lo >> 26; + this.words[i] = (int)(lo & 0x3ffffff); + } + + if (carry != 0) + { + Array.Resize(ref this.words, this.Length + 1); + this.words[i] = (int)carry; + this.Length++; + } + + return isNegNum ? this.Ineg() : this; + } + + /// + /// Multiply `this` by `num` + /// + /// + /// + /// + public BN Muln(long num) + { + return this.Clone().Imuln(num); + } + + /// + /// Square (`this` * `this`) + /// + /// + public BN Sqr() + { + return this.Mul(this); + } + + /// + /// Square (`this` * `this`) in-place + /// + /// + public BN Isqr() + { + return this.Imul(this.Clone()); + } + + /// + /// Raise `this` to the power of `num` + /// + /// + /// + public BN Pow(BN num) + { + var w = toBitArray(num); + if (w.Length == 0) return new BN(1); + + // Skip leading zeroes + var res = this; + int i; + for (i = 0; i < w.Length; i++, res = res.Sqr()) + { + if (w[i] != 0) break; + } + + if (++i < w.Length) + { + for (var q = res.Sqr(); i < w.Length; i++, q = q.Sqr()) + { + if (w[i] == 0) continue; + + res = res.Mul(q); + } + } + + return res; + } + + /// + /// Shift-left in-place + /// + /// + /// + /// + public BN Iushln(int bits) + { + Functions.Assert(bits >= 0); + + var r = bits % 26; + var s = (bits - r) / 26; + int carryMask = (0x3ffffff >> (26 - r)) << (26 - r); + int i; + + if (r != 0) + { + int carry = 0; + + for (i = 0; i < this.Length; i++) + { + var newCarry = this.words[i] & carryMask; + var c = ((this.words[i] | 0) - newCarry) << r; + this.words[i] = c | carry; + carry = newCarry >> (26 - r); + } + + if (carry != 0) + { + Array.Resize(ref this.words, this.Length + 1); + this.words[i] = carry; + this.Length++; + } + } + + if (s != 0) + { + Array.Resize(ref this.words, this.Length + s); + + for (i = this.Length - 1; i >= 0; i--) + { + this.words[i + s] = this.words[i]; + } + + for (i = 0; i < s; i++) + { + this.words[i] = 0; + } + + this.Length += s; + } + + return this._strip(); + } + + /// + /// Shift-left in-place + /// + /// + /// + /// + public BN Ishln(int bits) + { + // TODO(indutny): implement me + Functions.Assert(this.Negative == 0); + return this.Iushln(bits); + } + + /// + /// Shift-right in-place + /// + /// + /// A lowest bit before trailing zeroes + /// If is present - it will be filled with destroyed bits + /// + /// + public BN Iushrn(int bits, int hint = 0, BN? extended = null) + { + Functions.Assert(bits >= 0); + int h; + if (hint != 0) + { + h = (hint - (hint % 26)) / 26; + } + else + { + h = 0; + } + + var r = bits % 26; + var s = Math.Min((bits - r) / 26, this.Length); + var mask = 0x3ffffff ^ ((0x3ffffff >> r) << r); + var maskedWords = extended; + + h -= s; + h = Math.Max(0, h); + + int i; + // Extended mode, copy masked part + if (maskedWords != null) + { + Array.Resize(ref maskedWords.words, s); + for (i = 0; i < s; i++) + { + maskedWords.words[i] = this.words[i]; + } + maskedWords.Length = s; + } + + if (s == 0) + { + // No-op, we should not move anything at all + } + else if (this.Length > s) + { + this.Length -= s; + for (i = 0; i < this.Length; i++) + { + this.words[i] = this.words[i + s]; + } + } + else + { + this.words[0] = 0; + this.Length = 1; + } + + int carry = 0; + for (i = this.Length - 1; i >= 0 && (carry != 0 || i >= h); i--) + { + var word = this.words[i] | 0; + this.words[i] = (carry << (26 - r)) | (word >> r); + carry = word & mask; + } + + // Push carried bits as a mask + if (maskedWords != null && carry != 0) + { + Array.Resize(ref maskedWords.words, maskedWords.Length + 1); + maskedWords.words[maskedWords.Length++] = carry; + } + + if (this.Length == 0) + { + this.words[0] = 0; + this.Length = 1; + } + + return this._strip(); + } + + /// + /// Shift-right in-place + /// + /// + /// A lowest bit before trailing zeroes + /// If is present - it will be filled with destroyed bits + /// + /// + public BN Ishrn(int bits, int hint = 0, BN? extended = null) + { + // TODO(indutny): implement me + Functions.Assert(this.Negative == 0); + return this.Iushrn(bits, hint, extended); + } + + /// + /// Shift-left + /// + /// + /// + /// + public BN Shln(int bits) + { + return this.Clone().Ishln(bits); + } + + /// + /// Shift-left + /// + /// + /// + /// + public BN Ushln(int bits) + { + return this.Clone().Iushln(bits); + } + + /// + /// Shift-right + /// + /// + /// + public BN Shrn(int bits) + { + return this.Clone().Ishrn(bits); + } + + /// + /// Shift-right + /// + /// + /// + /// + public BN Ushrn(int bits) + { + return this.Clone().Iushrn(bits); + } + + /// + /// Test if n bit is set + /// + /// + /// + /// + public bool Testn(int bit) + { + Functions.Assert(bit >= 0); + var r = bit % 26; + var s = (bit - r) / 26; + var q = 1 << r; + + // Fast case: bit is much higher than all existing words + if (this.Length <= s) return false; + + // Check bit and return + var w = this.words[s]; + + return (w & q) != 0; + } + + /// + /// Clear bits with indexes higher or equal to `bits` + /// + /// + /// Only lowers bits of number (in-place) + /// + public BN Imaskn(int bits) + { + Functions.Assert(bits >= 0); + var r = bits % 26; + var s = (bits - r) / 26; + + Functions.Assert(this.Negative == 0, "imaskn works only with positive numbers"); + + if (this.Length <= s) + { + return this; + } + + if (r != 0) + { + s++; + } + this.Length = Math.Min(s, this.Length); + + if (r != 0) + { + var mask = 0x3ffffff ^ ((0x3ffffff >> r) << r); + this.words[this.Length - 1] &= mask; + } + + return this._strip(); + } + + /// + /// Clear bits with indexes higher or equal to `bits` + /// + /// + /// Only lowers bits of number + /// + public BN Maskn(int bits) + { + return this.Clone().Imaskn(bits); + } + + /// + /// Add plain number `num` to `this` + /// + /// + /// + /// + public BN Iaddn(int num) + { + Functions.Assert(num < 0x4000000); + if (num < 0) return this.Isubn(-num); + + // Possible sign change + if (this.Negative != 0) + { + if (this.Length == 1 && (this.words[0] | 0) <= num) + { + this.words[0] = num - (this.words[0] | 0); + this.Negative = 0; + return this; + } + + this.Negative = 0; + this.Isubn(num); + this.Negative = 1; + return this; + } + + // Add without checks + return this._iaddn(num); + } + + private BN _iaddn(int num) + { + this.words[0] += num; + + // Carry + int i; + for (i = 0; i < this.Length && this.words[i] >= 0x4000000; i++) + { + this.words[i] -= 0x4000000; + if (i == this.Length - 1) + { + Array.Resize(ref this.words, i + 2); + this.words[i + 1] = 1; + } + else + { + this.words[i + 1]++; + } + } + this.Length = Math.Max(this.Length, i + 1); + + return this; + } + + /// + /// Subtract plain number `num` from `this` + /// + /// + /// + /// + public BN Isubn(int num) + { + Functions.Assert(num < 0x4000000); + if (num < 0) return this.Iaddn(-num); + + if (this.Negative != 0) + { + this.Negative = 0; + this.Iaddn(num); + this.Negative = 1; + return this; + } + + this.words[0] -= num; + + if (this.Length == 1 && this.words[0] < 0) + { + this.words[0] = -this.words[0]; + this.Negative = 1; + } + else + { + // Carry + for (var i = 0; i < this.Length && this.words[i] < 0; i++) + { + this.words[i] += 0x4000000; + this.words[i + 1] -= 1; + } + } + + return this._strip(); + } + + /// + /// Addition + /// + /// + /// + /// + public BN Addn(int num) + { + return this.Clone().Iaddn(num); + } + + /// + /// Subtraction + /// + /// + /// + /// + public BN Subn(int num) + { + return this.Clone().Isubn(num); + } + + /// + /// Absolute value + /// + /// + public BN Iabs() + { + this.Negative = 0; + + return this; + } + + /// + /// Absolute value + /// + /// Absolute clone of `this` + public BN Abs() + { + return this.Clone().Iabs(); + } + + private BN _ishlnsubmul(BN num, long mul, int shift) + { + var len = num.Length + shift; + int i; + + this._expand(len); + + long w; + long carry = 0; + for (i = 0; i < num.Length; i++) + { + w = (this.words[i + shift] | 0) + carry; + var right = (num.words[i] | 0) * mul; + w -= right & 0x3ffffff; + carry = (w >> 26) - ((right / 0x4000000) | 0); + this.words[i + shift] = (int)(w & 0x3ffffff); + } + for (; i < this.Length - shift; i++) + { + w = (this.words[i + shift] | 0) + carry; + carry = w >> 26; + this.words[i + shift] = (int)(w & 0x3ffffff); + } + + if (carry == 0) return this._strip(); + + // Subtraction overflow + Functions.Assert(carry == -1); + carry = 0; + for (i = 0; i < this.Length; i++) + { + w = -(this.words[i] | 0) + carry; + carry = w >> 26; + this.words[i] = (int)(w & 0x3ffffff); + } + this.Negative = 1; + + return this._strip(); + } + + private BNDivResult _wordDiv(BN num, DivMode mode = DivMode.None) + { + var shift = this.Length - num.Length; + + var a = this.Clone(); + var b = num; + + // Normalize + var bhi = b.words[b.Length - 1] | 0; + var bhiBits = BN._countBits(bhi); + shift = 26 - bhiBits; + if (shift != 0) + { + b = b.Ushln(shift); + a.Iushln(shift); + bhi = b.words[b.Length - 1] | 0; + } + + // Initialize quotient + var m = a.Length - b.Length; + BN? q = null; + + if (mode != DivMode.Mod) + { + q = new BN(); + q.Length = m + 1; + q.words = new int[q.Length]; + for (var i = 0; i < q.Length; i++) + { + q.words[i] = 0; + } + } + + var diff = a.Clone()._ishlnsubmul(b, 1, m); + if (diff.Negative == 0) + { + a = diff; + if (q != null) + { + q.words[m] = 1; + } + } + + for (var j = m - 1; j >= 0; j--) + { + long first = (b.Length + j < a.words.Length) ? a.words[b.Length + j] : 0; + long second = (b.Length + j - 1 < a.words.Length) ? a.words[b.Length + j - 1] : 0; + long qj = (first | 0) * 0x4000000 + + (second | 0); + + // NOTE: (qj / bhi) is (0x3ffffff * 0x4000000 + 0x3ffffff) / 0x2000000 max + // (0x7ffffff) + qj = Math.Min((qj / bhi) | 0, 0x3ffffff); + + a._ishlnsubmul(b, qj, j); + while (a.Negative != 0) + { + qj--; + a.Negative = 0; + a._ishlnsubmul(b, 1, j); + if (!a.IsZero()) + { + a.Negative ^= 1; + } + } + if (q != null) + { + q.words[j] = (int)qj; + } + } + if (q != null) + { + q._strip(); + } + a._strip(); + + // Denormalize + if (mode != DivMode.Div && shift != 0) + { + a.Iushrn(shift); + } + + return new BNDivResult { + Div = q ?? null, + Mod = a + }; + } + + /// + /// Quotient and modulus obtained by dividing + /// + /// + /// Can be set to `mod` to request mod only, to `div` to request div only, or be absent to request both div & mod + /// Is true if unsigned mod is requested + /// + /// + public BNDivResult Divmod(BN num, DivMode mode = DivMode.None, bool positive = false) + { + Functions.Assert(!num.IsZero()); + + if (this.IsZero()) + { + return new BNDivResult + { + Div = new BN(0), + Mod = new BN(0) + }; + } + + BN? div = null, mod = null; + BNDivResult res; + if (this.Negative != 0 && num.Negative == 0) + { + res = this.Neg().Divmod(num, mode); + + if (mode != DivMode.Mod) + { + div = res.Div.Neg(); + } + + if (mode != DivMode.Div) + { + mod = res.Mod.Neg(); + if (positive && mod.Negative != 0) + { + mod.Iadd(num); + } + } + + return new BNDivResult + { + Div = div, + Mod = mod + }; + } + + if (this.Negative == 0 && num.Negative != 0) + { + res = this.Divmod(num.Neg(), mode); + + if (mode != DivMode.Mod) + { + div = res.Div.Neg(); + } + + return new BNDivResult + { + Div = div, + Mod = res.Mod + }; + } + + if ((this.Negative & num.Negative) != 0) + { + res = this.Neg().Divmod(num.Neg(), mode); + + if (mode != DivMode.Div) + { + mod = res.Mod.Neg(); + if (positive && mod.Negative != 0) + { + mod.Isub(num); + } + } + + return new BNDivResult + { + Div = res.Div, + Mod = mod + }; + } + + // Both numbers are positive at this point + + // Strip both numbers to approximate shift value + if (num.Length > this.Length || this.Cmp(num) < 0) + { + return new BNDivResult + { + Div = new BN(0), + Mod = this + }; + } + + // Very short reduction + if (num.Length == 1) + { + if (mode == DivMode.Div) + { + return new BNDivResult + { + Div = this.Divn(num.words[0]), + Mod = null + }; + } + + if (mode == DivMode.Mod) + { + return new BNDivResult + { + Div = null, + Mod = new BN(this.Modrn(num.words[0])) + }; + } + + return new BNDivResult + { + Div = this.Divn(num.words[0]), + Mod = new BN(this.Modrn(num.words[0])) + }; + } + + return this._wordDiv(num, mode); + } + + /// + /// Divide (`this` / `num`) + /// + /// + /// + /// + public BN Div(BN num) + { + return this.Divmod(num, DivMode.Div, false).Div; + } + + /// + /// Reduct (`this` % `num`) + /// + /// + /// + /// + public BN Mod(BN num) + { + return this.Divmod(num, DivMode.Mod, false).Mod; + } + + /// + /// Reduct (`this` % `num`) + /// + /// + /// + /// + public BN Umod(BN num) + { + return this.Divmod(num, DivMode.Mod, true).Mod; + } + + /// + /// Rounded division (`this` / `num`) + /// + /// + /// + /// + public BN DivRound(BN num) + { + var dm = this.Divmod(num); + + // Fast case - exact division + if (dm.Mod.IsZero()) return dm.Div; + + var mod = dm.Div.Negative != 0 ? dm.Mod.Isub(num) : dm.Mod; + + var half = num.Ushrn(1); + var r2 = num.Andln(1); + var cmp = mod.Cmp(half); + + // Round down + if (cmp < 0 || (r2 == 1 && cmp == 0)) return dm.Div; + + // Round up + return dm.Div.Negative != 0 ? dm.Div.Isubn(1) : dm.Div.Iaddn(1); + } + + /// + /// + /// + /// + /// + /// + public int Modrn(int num) + { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + Functions.Assert(num <= 0x3ffffff); + long p = (1 << 26) % num; + + var acc = 0; + for (var i = this.Length - 1; i >= 0; i--) + { + acc = (int)((p * acc + (this.words[i] | 0)) % num); + } + + return isNegNum ? -acc : acc; + } + + /// + /// + /// + /// + /// + /// + [Obsolete("DEPRECATED")] + public int Modn(int num) + { + return this.Modrn(num); + } + + /// + /// In-place division by number + /// + /// + /// + /// + public BN Idivn(int num) + { + var isNegNum = num < 0; + if (isNegNum) num = -num; + + Functions.Assert(num <= 0x3ffffff); + + long carry = 0; + for (var i = this.Length - 1; i >= 0; i--) + { + long w = (this.words[i] | 0) + carry * 0x4000000; + this.words[i] = (int)((w / num) | 0); + carry = w % num; + } + + this._strip(); + return isNegNum ? this.Ineg() : this; + } + + /// + /// Division by number + /// + /// + /// + /// + public BN Divn(int num) + { + return this.Clone().Idivn(num); + } + + /// + /// + /// + /// + /// Extended GCD results + /// + public BNExtendedGCDResult Egcd(BN p) + { + Functions.Assert(p.Negative == 0); + Functions.Assert(!p.IsZero()); + + var x = this; + var y = p.Clone(); + + if (x.Negative != 0) + { + x = x.Umod(p); + } + else + { + x = x.Clone(); + } + + // A * x + B * y = x + var A = new BN(1); + var B = new BN(0); + + // C * x + D * y = y + var C = new BN(0); + var D = new BN(1); + + var g = 0; + + while (x.IsEven() && y.IsEven()) + { + x.Iushrn(1); + y.Iushrn(1); + ++g; + } + + var yp = y.Clone(); + var xp = x.Clone(); + + while (!x.IsZero()) + { + int i, im; + for (i = 0, im = 1; (x.words[0] & im) == 0 && i < 26; ++i, im <<= 1) ; + if (i > 0) + { + x.Iushrn(i); + while (i-- > 0) + { + if (A.IsOdd() || B.IsOdd()) + { + A.Iadd(yp); + B.Isub(xp); + } + + A.Iushrn(1); + B.Iushrn(1); + } + } + + int j, jm; + for (j = 0, jm = 1; (y.words[0] & jm) == 0 && j < 26; ++j, jm <<= 1) ; + if (j > 0) + { + y.Iushrn(j); + while (j-- > 0) + { + if (C.IsOdd() || D.IsOdd()) + { + C.Iadd(yp); + D.Isub(xp); + } + + C.Iushrn(1); + D.Iushrn(1); + } + } + + if (x.Cmp(y) >= 0) + { + x.Isub(y); + A.Isub(C); + B.Isub(D); + } + else + { + y.Isub(x); + C.Isub(A); + D.Isub(B); + } + } + + return new BNExtendedGCDResult { + A = C, + B = D, + Gcd = y.Iushln(g) + }; + } + + // This is reduced incarnation of the binary EEA + // above, designated to invert members of the + // _prime_ fields F(p) at a maximal speed + internal BN _invmp(BN p) + { + Functions.Assert(p.Negative == 0); + Functions.Assert(!p.IsZero()); + + var a = this; + var b = p.Clone(); + + if (a.Negative != 0) + { + a = a.Umod(p); + } + else + { + a = a.Clone(); + } + + var x1 = new BN(1); + var x2 = new BN(0); + + var delta = b.Clone(); + + while (a.Cmpn(1) > 0 && b.Cmpn(1) > 0) + { + int i, im; + for (i = 0, im = 1; (a.words[0] & im) == 0 && i < 26; ++i, im <<= 1) ; + if (i > 0) + { + a.Iushrn(i); + while (i-- > 0) + { + if (x1.IsOdd()) + { + x1.Iadd(delta); + } + + x1.Iushrn(1); + } + } + + int j, jm; + for (j = 0, jm = 1; (b.words[0] & jm) == 0 && j < 26; ++j, jm <<= 1) ; + if (j > 0) + { + b.Iushrn(j); + while (j-- > 0) + { + if (x2.IsOdd()) + { + x2.Iadd(delta); + } + + x2.Iushrn(1); + } + } + + if (a.Cmp(b) >= 0) + { + a.Isub(b); + x1.Isub(x2); + } + else + { + b.Isub(a); + x2.Isub(x1); + } + } + + BN res; + if (a.Cmpn(1) == 0) + { + res = x1; + } + else + { + res = x2; + } + + if (res.Cmpn(0) < 0) + { + res.Iadd(p); + } + + return res; + } + + /// + /// GCD + /// + /// + /// + /// + public BN Gcd(BN num) + { + if (this.IsZero()) return num.Abs(); + if (num.IsZero()) return this.Abs(); + + var a = this.Clone(); + var b = num.Clone(); + a.Negative = 0; + b.Negative = 0; + + // Remove common factor of two + int shift; + for (shift = 0; a.IsEven() && b.IsEven(); shift++) + { + a.Iushrn(1); + b.Iushrn(1); + } + + do + { + while (a.IsEven()) + { + a.Iushrn(1); + } + while (b.IsEven()) + { + b.Iushrn(1); + } + + var r = a.Cmp(b); + if (r < 0) + { + // Swap `a` and `b` to make `a` always bigger than `b` + var t = a; + a = b; + b = t; + } + else if (r == 0 || b.Cmpn(1) == 0) + { + break; + } + + a.Isub(b); + } while (true); + + return b.Iushln(shift); + } + + /// + /// Inverse `this` modulo `num` + /// + /// + /// + /// + public BN Invm(BN num) + { + return this.Egcd(num).A.Umod(num); + } + + /// + /// + /// + /// True if the number is even + public bool IsEven() + { + return (this.words[0] & 1) == 0; + } + + /// + /// + /// + /// True if the number is odd + public bool IsOdd() + { + return (this.words[0] & 1) == 1; + } + + /// + /// And first word and num + /// + /// + /// + public int Andln(int num) + { + return this.words[0] & num; + } + + /// + /// Increment at the bit position in-line + /// Add 1 << `bit` to the number + /// + /// + /// + public BN Bincn(int bit) + { + var r = bit % 26; + var s = (bit - r) / 26; + var q = 1 << r; + + // Fast case: bit is much higher than all existing words + if (this.Length <= s) + { + this._expand(s + 1); + this.words[s] |= q; + return this; + } + + // Add bit and propagate, if needed + var carry = q; + int i; + for (i = s; carry != 0 && i < this.Length; i++) + { + var w = this.words[i] | 0; + w += carry; + carry = w >> 26; + w &= 0x3ffffff; + this.words[i] = w; + } + if (carry != 0) + { + this.words[i] = carry; + this.Length++; + } + return this; + } + + /// + /// + /// + /// True if the number is zero + public bool IsZero() + { + return this.Length == 1 && this.words[0] == 0; + } + + /// + /// Compare two numbers + /// + /// + /// -1 (`this` < `num`), 0 (`this` == `num`), or 1 (`this` > `num`) depending on the comparison result (ucmp, cmpn) + /// + public int Cmpn(int num) + { + var negative = num < 0; + + if (this.Negative != 0 && !negative) return -1; + if (this.Negative == 0 && negative) return 1; + + this._strip(); + + int res; + if (this.Length > 1) + { + res = 1; + } + else + { + if (negative) + { + num = -num; + } + + Functions.Assert(num <= 0x3ffffff, "Number is too big"); + + var w = this.words[0] | 0; + res = w == num ? 0 : w < num ? -1 : 1; + } + if (this.Negative != 0) return -res | 0; + return res; + } + + /// + /// Compare two numbers + /// + /// + /// -1 (`this` < `num`), 0 (`this` == `num`), or 1 (`this` > `num`) depending on the comparison result (ucmp, cmpn) + public int Cmp(BN num) + { + if (this.Negative != 0 && num.Negative == 0) return -1; + if (this.Negative == 0 && num.Negative != 0) return 1; + + var res = this.Ucmp(num); + if (this.Negative != 0) return -res | 0; + return res; + } + + /// + /// Unsigned comparison + /// + /// + /// + public int Ucmp(BN num) + { + // At this point both numbers have the same sign + if (this.Length > num.Length) return 1; + if (this.Length < num.Length) return -1; + + var res = 0; + for (var i = this.Length - 1; i >= 0; i--) + { + var a = this.words[i] | 0; + var b = num.words[i] | 0; + + if (a == b) continue; + if (a < b) + { + res = -1; + } + else if (a > b) + { + res = 1; + } + break; + } + return res; + } + + /// + /// Compare two numbers + /// + /// + /// True if (`this` > `num`) + /// + public bool Gtn(int num) + { + return this.Cmpn(num) == 1; + } + + /// + /// Compare two numbers + /// + /// + /// True if (`this` > `num`) + public bool Gt(BN num) + { + return this.Cmp(num) == 1; + } + + /// + /// Compare two numbers + /// + /// + /// True if (`this` >= `num`) + /// + public bool Gten(int num) + { + return this.Cmpn(num) >= 0; + } + + /// + /// Compare two numbers + /// + /// + /// True if (`this` >= `num`) + public bool Gte(BN num) + { + return this.Cmp(num) >= 0; + } + + /// + /// Compare two numbers + /// + /// + /// True if (`this` < `num`) + /// + public bool Ltn(int num) + { + return this.Cmpn(num) == -1; + } + + /// + /// Compare two numbers + /// + /// + /// True if (`this` < `num`) + public bool Lt(BN num) + { + return this.Cmp(num) == -1; + } + + /// + /// Compare two numbers + /// + /// + /// True if (`this` <= `num`) + /// + public bool Lten(int num) + { + return this.Cmpn(num) <= 0; + } + + /// + /// Compare two numbers + /// + /// + /// True if (`this` <= `num`) + public bool Lte(BN num) + { + return this.Cmp(num) <= 0; + } + + /// + /// Compare two numbers + /// + /// + /// True if (`this` == `num`) + /// + public bool Eqn(int num) + { + return this.Cmpn(num) == 0; + } + + /// + /// Compare two numbers + /// + /// + /// True if (`this` == `num`) + public bool Eq(BN num) + { + return this.Cmp(num) == 0; + } + + public static Red Red(string num) + { + return new Red(num); + } + + public static Red Red(BN num) + { + return new Red(num); + } + + /// + /// + /// + /// + /// + /// + public BN ToRed(Red ctx) + { + Functions.Assert(this.red == null, "Already a number in reduction context"); + Functions.Assert(this.Negative == 0, "red works only with positives"); + return ctx.ConvertTo(this)._forceRed(ctx); + } + + /// + /// + /// + /// + /// + public BN FromRed() + { + Functions.Assert(this.red != null, "fromRed works only with numbers in reduction context"); + return this.red.ConvertFrom(this); + } + + internal BN _forceRed(Red ctx) + { + this.red = ctx; + return this; + } + + /// + /// + /// + /// + /// + /// + public BN ForceRed(Red ctx) + { + Functions.Assert(this.red == null, "Already a number in reduction context"); + return this._forceRed(ctx); + } + + /// + /// + /// + /// + /// + /// + public BN RedAdd(BN num) + { + Functions.Assert(this.red != null, "redAdd works only with red numbers"); + return this.red.Add(this, num); + } + + /// + /// + /// + /// + /// + /// + public BN RedIAdd(BN num) + { + Functions.Assert(this.red != null, "redIAdd works only with red numbers"); + return this.red.Iadd(this, num); + } + + /// + /// + /// + /// + /// + /// + public BN RedSub(BN num) + { + Functions.Assert(this.red != null, "redSub works only with red numbers"); + return this.red.Sub(this, num); + } + + /// + /// + /// + /// + /// + /// + public BN RedISub(BN num) + { + Functions.Assert(this.red != null, "redISub works only with red numbers"); + return this.red.Isub(this, num); + } + + /// + /// + /// + /// + /// + /// + public BN RedShl(int num) + { + Functions.Assert(this.red != null, "redShl works only with red numbers"); + return this.red.Shl(this, num); + } + + /// + /// + /// + /// + /// + /// + public BN RedMul(BN num) + { + Functions.Assert(this.red != null, "redMul works only with red numbers"); + this.red._verify2(this, num); + return this.red.Mul(this, num); + } + + /// + /// + /// + /// + /// + /// + public BN RedIMul(BN num) + { + Functions.Assert(this.red != null, "redMul works only with red numbers"); + this.red._verify2(this, num); + return this.red.Imul(this, num); + } + + /// + /// + /// + /// + /// + public BN RedSqr() + { + Functions.Assert(this.red != null, "redSqr works only with red numbers"); + this.red._verify1(this); + return this.red.Sqr(this); + } + + /// + /// + /// + /// + /// + public BN RedISqr() + { + Functions.Assert(this.red != null, "redISqr works only with red numbers"); + this.red._verify1(this); + return this.red.Isqr(this); + } + + // Square root over p + /// + /// + /// + /// + /// + public BN RedSqrt() + { + Functions.Assert(this.red != null, "redSqrt works only with red numbers"); + this.red._verify1(this); + return this.red.Sqrt(this); + } + + /// + /// + /// + /// + /// + public BN RedInvm() + { + Functions.Assert(this.red != null, "redInvm works only with red numbers"); + this.red._verify1(this); + return this.red.Invm(this); + } + + // Return negative clone of `this` % `red modulo` + /// + /// + /// + /// + /// + public BN RedNeg() + { + Functions.Assert(this.red != null, "redNeg works only with red numbers"); + this.red._verify1(this); + return this.red.Neg(this); + } + + /// + /// + /// + /// + /// + /// + public BN RedPow(BN num) + { + Functions.Assert(this.red != null && num.red == null, "redPow(normalNum)"); + this.red._verify1(this); + return this.red.Pow(this, num); + } + + // Prime numbers with efficient reduction + private static Dictionary primes = new() { + { "k256", null }, + { "p224", null }, + { "p192", null }, + { "p25519", null } + }; + + /// + /// Exported mostly for testing purposes, use plain name instead + /// + /// + /// + /// + public static MPrime Prime(string name) + { + // Cached version of prime + if (primes[name] != null) return primes[name]; + + MPrime prime; + if (name == "k256") + { + prime = new K256(); + } + else if (name == "p224") + { + prime = new P224(); + } + else if (name == "p192") + { + prime = new P192(); + } + else if (name == "p25519") + { + prime = new P25519(); + } + else + { + throw new BNException("Unknown prime " + name); + } + primes[name] = prime; + + return prime; + } + + /// + /// + /// + /// + /// + /// + public static Mont Mont(string num) + { + return new Mont(num); + } + + /// + /// + /// + /// + /// + /// + public static Mont Mont(BN num) + { + return new Mont(num); + } + } +} \ No newline at end of file diff --git a/BNSharp/BNDivResult.cs b/BNSharp/BNDivResult.cs new file mode 100644 index 0000000..a7cfdf8 --- /dev/null +++ b/BNSharp/BNDivResult.cs @@ -0,0 +1,8 @@ +namespace BNSharp +{ + public class BNDivResult + { + public BN? Div { get; set; } + public BN? Mod { get; set; } + } +} diff --git a/BNSharp/BNException.cs b/BNSharp/BNException.cs new file mode 100644 index 0000000..8a85d67 --- /dev/null +++ b/BNSharp/BNException.cs @@ -0,0 +1,9 @@ +namespace BNSharp +{ + public class BNException : Exception + { + public BNException() : base() { } + + public BNException(string message) : base(message) { } + } +} diff --git a/BNSharp/BNExtendedGCDResult.cs b/BNSharp/BNExtendedGCDResult.cs new file mode 100644 index 0000000..25a90b4 --- /dev/null +++ b/BNSharp/BNExtendedGCDResult.cs @@ -0,0 +1,9 @@ +namespace BNSharp +{ + public class BNExtendedGCDResult + { + public BN? A { get; set; } + public BN? B { get; set; } + public BN? Gcd { get; set; } + } +} diff --git a/BNSharp/BNSharp.csproj b/BNSharp/BNSharp.csproj new file mode 100644 index 0000000..ea10488 --- /dev/null +++ b/BNSharp/BNSharp.csproj @@ -0,0 +1,32 @@ + + + + net6.0 + enable + enable + True + 1.0.0 + XeroXP + BigNum in C#. Port of bn.js + Copyright (c) $(Authors) 2022 + https://github.com/XeroXP/BNSharp + https://github.com/XeroXP/BNSharp.git + git + bn, bignum, bignumber + README.md + LICENSE + False + + + + + True + \ + + + True + \ + + + + diff --git a/BNSharp/DivMode.cs b/BNSharp/DivMode.cs new file mode 100644 index 0000000..3469b83 --- /dev/null +++ b/BNSharp/DivMode.cs @@ -0,0 +1,9 @@ +namespace BNSharp +{ + public enum DivMode + { + None, + Mod, + Div + } +} diff --git a/BNSharp/Endian.cs b/BNSharp/Endian.cs new file mode 100644 index 0000000..3a4aedd --- /dev/null +++ b/BNSharp/Endian.cs @@ -0,0 +1,8 @@ +namespace BNSharp +{ + public enum Endian + { + LittleEndian, + BigEndian + } +} diff --git a/BNSharp/Extensions/NumberExtensions.cs b/BNSharp/Extensions/NumberExtensions.cs new file mode 100644 index 0000000..a6dd038 --- /dev/null +++ b/BNSharp/Extensions/NumberExtensions.cs @@ -0,0 +1,48 @@ +namespace BNSharp.Extensions +{ + internal static class NumberExtensions + { + public static string ToString(this int num, int @base) + { + return DecimalToArbitrarySystem(num, @base); + } + + /// + /// Converts the given decimal number to the numeral system with the + /// specified radix (in the range [2, 36]). + /// + /// The number to convert. + /// The radix of the destination numeral system (in the range [2, 36]). + /// + private static string DecimalToArbitrarySystem(long decimalNumber, int radix) + { + const int BitsInLong = 64; + const string Digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + if (radix < 2 || radix > Digits.Length) + throw new ArgumentException("The radix must be >= 2 and <= " + Digits.Length.ToString()); + + if (decimalNumber == 0) + return "0"; + + int index = BitsInLong - 1; + long currentNumber = Math.Abs(decimalNumber); + char[] charArray = new char[BitsInLong]; + + while (currentNumber != 0) + { + int remainder = (int)(currentNumber % radix); + charArray[index--] = Digits[remainder]; + currentNumber = currentNumber / radix; + } + + string result = new string(charArray, index + 1, BitsInLong - index - 1); + if (decimalNumber < 0) + { + result = "-" + result; + } + + return result.ToLowerInvariant(); + } + } +} diff --git a/BNSharp/FFTM.cs b/BNSharp/FFTM.cs new file mode 100644 index 0000000..33fb416 --- /dev/null +++ b/BNSharp/FFTM.cs @@ -0,0 +1,233 @@ +namespace BNSharp +{ + // Cooley-Tukey algorithm for FFT + // slightly revisited to rely on looping instead of recursion + internal class FFTM + { + private int x; + private int y; + + public FFTM(int x, int y) + { + this.x = x; + this.y = y; + } + + public int[] MakeRBT(int N) + { + var t = new int[N]; + var l = BN._countBits(N) - 1; + for (var i = 0; i < N; i++) + { + t[i] = this.RevBin(i, l, N); + } + + return t; + } + + // Returns binary-reversed representation of `x` + public int RevBin(int x, int l, int N) + { + if (x == 0 || x == N - 1) return x; + + var rb = 0; + for (var i = 0; i < l; i++) + { + rb |= (x & 1) << (l - i - 1); + x >>= 1; + } + + return rb; + } + + // Performs "tweedling" phase, therefore 'emulating' + // behaviour of the recursive algorithm + public void Permute(int[] rbt, int[] rws, int[] iws, int[] rtws, int[] itws, int N) + { + for (var i = 0; i < N; i++) + { + rtws[i] = rws[rbt[i]]; + itws[i] = iws[rbt[i]]; + } + } + + public void Transform(int[] rws, int[] iws, int[] rtws, int[] itws, int N, int[] rbt) + { + this.Permute(rbt, rws, iws, rtws, itws, N); + + for (var s = 1; s < N; s <<= 1) + { + var l = s << 1; + + var rtwdf = (int)Math.Cos(2 * Math.PI / l); + var itwdf = (int)Math.Sin(2 * Math.PI / l); + + for (var p = 0; p < N; p += l) + { + var rtwdf_ = rtwdf; + var itwdf_ = itwdf; + + for (var j = 0; j < s; j++) + { + var re = rtws[p + j]; + var ie = itws[p + j]; + + var ro = rtws[p + j + s]; + var io = itws[p + j + s]; + + var rx = rtwdf_ * ro - itwdf_ * io; + + io = rtwdf_ * io + itwdf_ * ro; + ro = rx; + + rtws[p + j] = re + ro; + itws[p + j] = ie + io; + + rtws[p + j + s] = re - ro; + itws[p + j + s] = ie - io; + + /* jshint maxdepth : false */ + if (j != l) + { + rx = rtwdf * rtwdf_ - itwdf * itwdf_; + + itwdf_ = rtwdf * itwdf_ + itwdf * rtwdf_; + rtwdf_ = rx; + } + } + } + } + } + + public int GuessLen13b(int n, int m) + { + var N = Math.Max(m, n) | 1; + var odd = N & 1; + var i = 0; + for (N = N / 2 | 0; N != 0; N = N >> 1) + { + i++; + } + + return 1 << i + 1 + odd; + } + + public void Conjugate(int[] rws, int[] iws, int N) + { + if (N <= 1) return; + + for (var i = 0; i < N / 2; i++) + { + var t = rws[i]; + + rws[i] = rws[N - i - 1]; + rws[N - i - 1] = t; + + t = iws[i]; + + iws[i] = -iws[N - i - 1]; + iws[N - i - 1] = -t; + } + } + + public int[] Normalize13b(int[] ws, int N) + { + var carry = 0; + for (var i = 0; i < N / 2; i++) + { + var w = (int)Math.Round((double)ws[2 * i + 1] / N) * 0x2000 + + (int)Math.Round((double)ws[2 * i] / N) + + carry; + + ws[i] = w & 0x3ffffff; + + if (w < 0x4000000) + { + carry = 0; + } + else + { + carry = w / 0x4000000 | 0; + } + } + + return ws; + } + + public void Convert13b(int[] ws, int len, int[] rws, int N) + { + var carry = 0; + int i; + for (i = 0; i < len; i++) + { + carry = carry + (ws[i] | 0); + + rws[2 * i] = carry & 0x1fff; carry = carry >> 13; + rws[2 * i + 1] = carry & 0x1fff; carry = carry >> 13; + } + + // Pad with zeroes + for (i = 2 * len; i < N; ++i) + { + rws[i] = 0; + } + + if (carry != 0) throw new BNException(); + if ((carry & ~0x1fff) != 0) throw new BNException(); + } + + public int[] Stub(int N) + { + var ph = new int[N]; + for (var i = 0; i < N; i++) + { + ph[i] = 0; + } + + return ph; + } + + public BN Mulp(BN x, BN y, BN @out) + { + var N = 2 * this.GuessLen13b(x.Length, y.Length); + + var rbt = this.MakeRBT(N); + + var _ = this.Stub(N); + + var rws = new int[N]; + var rwst = new int[N]; + var iwst = new int[N]; + + var nrws = new int[N]; + var nrwst = new int[N]; + var niwst = new int[N]; + + var rmws = new int[N]; + Array.Copy(@out.words, 0, rmws, 0, Math.Min(@out.words.Length, N)); + + this.Convert13b(x.words, x.Length, rws, N); + this.Convert13b(y.words, y.Length, nrws, N); + + this.Transform(rws, _, rwst, iwst, N, rbt); + this.Transform(nrws, _, nrwst, niwst, N, rbt); + + for (var i = 0; i < N; i++) + { + var rx = rwst[i] * nrwst[i] - iwst[i] * niwst[i]; + iwst[i] = rwst[i] * niwst[i] + iwst[i] * nrwst[i]; + rwst[i] = rx; + } + + this.Conjugate(rwst, iwst, N); + this.Transform(rwst, iwst, rmws, _, N, rbt); + this.Conjugate(rmws, _, N); + this.Normalize13b(rmws, N); + + @out.Negative = x.Negative ^ y.Negative; + @out.Length = x.Length + y.Length; + @out.words = rmws; //? + return @out._strip(); + } + } +} diff --git a/BNSharp/Functions.cs b/BNSharp/Functions.cs new file mode 100644 index 0000000..ff2cef6 --- /dev/null +++ b/BNSharp/Functions.cs @@ -0,0 +1,10 @@ +namespace BNSharp +{ + internal class Functions + { + public static void Assert(bool val, string? msg = null) + { + if (!val) throw new BNException(msg ?? "Assertion failed"); + } + } +} diff --git a/BNSharp/K256.cs b/BNSharp/K256.cs new file mode 100644 index 0000000..9d9fb7b --- /dev/null +++ b/BNSharp/K256.cs @@ -0,0 +1,84 @@ +namespace BNSharp +{ + public class K256 : MPrime + { + public K256() : base("k256", "ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f") + { + + } + + public override void Split(BN input, BN output) + { + // 256 = 9 * 26 + 22 + var mask = 0x3fffff; + + var outLen = Math.Min(input.Length, 9); + Array.Resize(ref output.words, outLen); + int i; + for (i = 0; i < outLen; i++) + { + output.words[i] = input.words[i]; + } + output.Length = outLen; + + if (input.Length <= 9) + { + input.words[0] = 0; + input.Length = 1; + return; + } + + // Shift by 9 limbs + var prev = input.words[9]; + Array.Resize(ref output.words, output.Length + 1); + output.words[output.Length++] = prev & mask; + + for (i = 10; i < input.Length; i++) + { + var next = input.words[i] | 0; + input.words[i - 10] = ((next & mask) << 4) | (prev >> 22); + prev = next; + } + prev >>= 22; + input.words[i - 10] = prev; + if (prev == 0 && input.Length > 10) + { + input.Length -= 10; + } + else + { + input.Length -= 9; + } + } + + public override BN ImulK(BN num) + { + // K = 0x1000003d1 = [ 0x40, 0x3d1 ] + Array.Resize(ref num.words, num.Length + 2); + num.words[num.Length] = 0; + num.words[num.Length + 1] = 0; + num.Length += 2; + + // bounded at: 0x40 * 0x3ffffff + 0x3d0 = 0x100000390 + long lo = 0; + for (var i = 0; i < num.Length; i++) + { + var w = (long)num.words[i] | 0; + lo += w * 0x3d1; + num.words[i] = (int)(lo & 0x3ffffff); + lo = w * 0x40 + ((lo / 0x4000000) | 0); + } + + // Fast length reduction + if (num.words[num.Length - 1] == 0) + { + num.Length--; + if (num.words[num.Length - 1] == 0) + { + num.Length--; + } + } + return num; + } + } +} diff --git a/BNSharp/MPrime.cs b/BNSharp/MPrime.cs new file mode 100644 index 0000000..3eaac95 --- /dev/null +++ b/BNSharp/MPrime.cs @@ -0,0 +1,72 @@ +namespace BNSharp +{ + public class MPrime + { + public string name; + public BN p; + private int n; + private BN k; + private BN tmp; + + public MPrime(string name, string p) + { + this.name = name; + this.p = new BN(p, 16); + this.n = this.p.BitLength(); + this.k = new BN(1).Iushln(this.n).Isub(this.p); + + this.tmp = this._tmp(); + } + + private BN _tmp() + { + var tmp = new BN(); + tmp.words = new int[(int)Math.Ceiling((double)this.n / 13)]; + return tmp; + } + + public BN Ireduce(BN num) + { + // Assumes that `num` is less than `P^2` + // num = HI * (2 ^ N - K) + HI * K + LO = HI * K + LO (mod P) + var r = num; + int rlen; + + do + { + this.Split(r, this.tmp); + r = this.ImulK(r); + r = r.Iadd(this.tmp); + rlen = r.BitLength(); + } while (rlen > this.n); + + var cmp = rlen < this.n ? -1 : r.Ucmp(this.p); + if (cmp == 0) + { + r.words[0] = 0; + r.Length = 1; + } + else if (cmp > 0) + { + r.Isub(this.p); + } + else + { + // r is a BN v5 instance + r._strip(); + } + + return r; + } + + public virtual void Split(BN input, BN @out) + { + input.Iushrn(this.n, 0, @out); + } + + public virtual BN ImulK(BN num) + { + return num.Imul(this.k); + } + } +} diff --git a/BNSharp/Mont.cs b/BNSharp/Mont.cs new file mode 100644 index 0000000..5590fe7 --- /dev/null +++ b/BNSharp/Mont.cs @@ -0,0 +1,137 @@ +namespace BNSharp +{ + // + // A reduce context, could be using montgomery or something better, depending + // on the `m` itself. + // + public class Mont : Red + { + private int shift; + private BN r; + private BN r2; + private BN rinv; + private BN minv; + + /// + /// + /// + /// + /// + public Mont(string m) : base(m) + { + _init(); + } + + /// + /// + /// + /// + /// + public Mont(BN m) : base(m) + { + _init(); + } + + private void _init() + { + this.shift = this.m.BitLength(); + if (this.shift % 26 != 0) + { + this.shift += 26 - (this.shift % 26); + } + + this.r = new BN(1).Iushln(this.shift); + this.r2 = this.Imod(this.r.Sqr()); + this.rinv = this.r._invmp(this.m); + + this.minv = this.rinv.Mul(this.r).Isubn(1).Div(this.m); + this.minv = this.minv.Umod(this.r); + this.minv = this.r.Sub(this.minv); + } + + /// + /// + /// + /// + /// + /// + /// + public override BN Imul(BN a, BN b) + { + if (a.IsZero() || b.IsZero()) + { + a.words[0] = 0; + a.Length = 1; + return a; + } + + var t = a.Imul(b); + var c = t.Maskn(this.shift).Mul(this.minv).Imaskn(this.shift).Mul(this.m); + var u = t.Isub(c).Iushrn(this.shift); + var res = u; + + if (u.Cmp(this.m) >= 0) + { + res = u.Isub(this.m); + } + else if (u.Cmpn(0) < 0) + { + res = u.Iadd(this.m); + } + + return res._forceRed(this); + } + + /// + /// + /// + /// + /// + /// + /// + public override BN Mul(BN a, BN b) + { + if (a.IsZero() || b.IsZero()) return new BN(0)._forceRed(this); + + var t = a.Mul(b); + var c = t.Maskn(this.shift).Mul(this.minv).Imaskn(this.shift).Mul(this.m); + var u = t.Isub(c).Iushrn(this.shift); + var res = u; + if (u.Cmp(this.m) >= 0) + { + res = u.Isub(this.m); + } + else if (u.Cmpn(0) < 0) + { + res = u.Iadd(this.m); + } + + return res._forceRed(this); + } + + /// + /// + /// + /// + /// + /// + public override BN Invm(BN a) + { + // (AR)^-1 * R^2 = (A^-1 * R^-1) * R^2 = A^-1 * R + var res = this.Imod(a._invmp(this.m).Mul(this.r2)); + return res._forceRed(this); + } + + public override BN ConvertTo(BN num) + { + return this.Imod(num.Ushln(this.shift)); + } + + public override BN ConvertFrom(BN num) + { + var r = this.Imod(num.Mul(this.rinv)); + r.red = null; + return r; + } + } +} diff --git a/BNSharp/P192.cs b/BNSharp/P192.cs new file mode 100644 index 0000000..793f4f9 --- /dev/null +++ b/BNSharp/P192.cs @@ -0,0 +1,10 @@ +namespace BNSharp +{ + public class P192 : MPrime + { + public P192() : base("p192", "ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff") + { + + } + } +} diff --git a/BNSharp/P224.cs b/BNSharp/P224.cs new file mode 100644 index 0000000..954c92d --- /dev/null +++ b/BNSharp/P224.cs @@ -0,0 +1,10 @@ +namespace BNSharp +{ + public class P224 : MPrime + { + public P224() : base("p224", "ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001") + { + + } + } +} diff --git a/BNSharp/P25519.cs b/BNSharp/P25519.cs new file mode 100644 index 0000000..c245008 --- /dev/null +++ b/BNSharp/P25519.cs @@ -0,0 +1,31 @@ +namespace BNSharp +{ + public class P25519 : MPrime + { + // 2 ^ 255 - 19 + public P25519() : base("25519", "7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed") + { + + } + + public override BN ImulK(BN num) + { + // K = 0x13 + var carry = 0; + for (var i = 0; i < num.Length; i++) + { + var hi = (num.words[i] | 0) * 0x13 + carry; + var lo = hi & 0x3ffffff; + hi >>= 26; + + num.words[i] = lo; + carry = hi; + } + if (carry != 0) + { + num.words[num.Length++] = carry; + } + return num; + } + } +} diff --git a/BNSharp/Red.cs b/BNSharp/Red.cs new file mode 100644 index 0000000..5e21ddf --- /dev/null +++ b/BNSharp/Red.cs @@ -0,0 +1,372 @@ +namespace BNSharp +{ + // + // A reduce context, could be using montgomery or something better, depending + // on the `m` itself. + // + public class Red + { + protected BN m; + public MPrime? prime; + + public Red(string m) + { + var prime = BN.Prime(m); + this.m = prime.p; + this.prime = prime; + } + + /// + /// + /// + /// + /// + public Red(BN m) + { + Functions.Assert(m.Gtn(1), "modulus must be greater than 1"); + this.m = m; + this.prime = null; + } + + internal void _verify1(BN a) + { + Functions.Assert(a.Negative == 0, "red works only with positives"); + Functions.Assert(a.red != null, "red works only with red numbers"); + } + + internal void _verify2(BN a, BN b) + { + Functions.Assert((a.Negative | b.Negative) == 0, "red works only with positives"); + Functions.Assert(a.red != null && a.red == b.red, + "red works only with red numbers"); + } + + /// + /// + /// + /// + /// + /// + public BN Imod(BN a) + { + if (this.prime != null) return this.prime.Ireduce(a)._forceRed(this); + + BN.move(a, a.Umod(this.m)._forceRed(this)); + return a; + } + + public BN Neg(BN a) + { + if (a.IsZero()) + { + return a.Clone(); + } + + return this.m.Sub(a)._forceRed(this); + } + + /// + /// + /// + /// + /// + /// + /// + public BN Add(BN a, BN b) + { + this._verify2(a, b); + + var res = a.Add(b); + if (res.Cmp(this.m) >= 0) + { + res.Isub(this.m); + } + return res._forceRed(this); + } + + /// + /// + /// + /// + /// + /// + /// + public BN Iadd(BN a, BN b) + { + this._verify2(a, b); + + var res = a.Iadd(b); + if (res.Cmp(this.m) >= 0) + { + res.Isub(this.m); + } + return res; + } + + /// + /// + /// + /// + /// + /// + /// + public BN Sub(BN a, BN b) + { + this._verify2(a, b); + + var res = a.Sub(b); + if (res.Cmpn(0) < 0) + { + res.Iadd(this.m); + } + return res._forceRed(this); + } + + /// + /// + /// + /// + /// + /// + /// + public BN Isub(BN a, BN b) + { + this._verify2(a, b); + + var res = a.Isub(b); + if (res.Cmpn(0) < 0) + { + res.Iadd(this.m); + } + return res; + } + + /// + /// + /// + /// + /// + /// + /// + public BN Shl(BN a, int num) + { + this._verify1(a); + return this.Imod(a.Ushln(num)); + } + + /// + /// + /// + /// + /// + /// + /// + public virtual BN Imul(BN a, BN b) + { + this._verify2(a, b); + return this.Imod(a.Imul(b)); + } + + /// + /// + /// + /// + /// + /// + /// + public virtual BN Mul(BN a, BN b) + { + this._verify2(a, b); + return this.Imod(a.Mul(b)); + } + + /// + /// + /// + /// + /// + /// + public BN Isqr(BN a) + { + return this.Imul(a, a.Clone()); + } + + /// + /// + /// + /// + /// + /// + public BN Sqr(BN a) + { + return this.Mul(a, a); + } + + /// + /// + /// + /// + /// + /// + public BN Sqrt(BN a) + { + if (a.IsZero()) return a.Clone(); + + var mod3 = this.m.Andln(3); + Functions.Assert(mod3 % 2 == 1); + + // Fast case + if (mod3 == 3) + { + var pow = this.m.Add(new BN(1)).Iushrn(2); + return this.Pow(a, pow); + } + + // Tonelli-Shanks algorithm (Totally unoptimized and slow) + // + // Find Q and S, that Q * 2 ^ S = (P - 1) + var q = this.m.Subn(1); + var s = 0; + while (!q.IsZero() && q.Andln(1) == 0) + { + s++; + q.Iushrn(1); + } + Functions.Assert(!q.IsZero()); + + var one = new BN(1).ToRed(this); + var nOne = one.RedNeg(); + + // Find quadratic non-residue + // NOTE: Max is such because of generalized Riemann hypothesis. + var lpow = this.m.Subn(1).Iushrn(1); + var z0 = this.m.BitLength(); + var z = new BN(2 * z0 * z0).ToRed(this); + + while (this.Pow(z, lpow).Cmp(nOne) != 0) + { + z.RedIAdd(nOne); + } + + var c = this.Pow(z, q); + var r = this.Pow(a, q.Addn(1).Iushrn(1)); + var t = this.Pow(a, q); + var m = s; + while (t.Cmp(one) != 0) + { + var tmp = t; + int i; + for (i = 0; tmp.Cmp(one) != 0; i++) + { + tmp = tmp.RedSqr(); + } + Functions.Assert(i < m); + var b = this.Pow(c, new BN(1).Iushln(m - i - 1)); + + r = r.RedMul(b); + c = b.RedSqr(); + t = t.RedMul(c); + m = i; + } + + return r; + } + + /// + /// + /// + /// + /// + /// + public virtual BN Invm(BN a) + { + var inv = a._invmp(this.m); + if (inv.Negative != 0) + { + inv.Negative = 0; + return this.Imod(inv).RedNeg(); + } + else + { + return this.Imod(inv); + } + } + + /// + /// + /// + /// + /// + /// + /// + public BN Pow(BN a, BN num) + { + if (num.IsZero()) return new BN(1).ToRed(this); + if (num.Cmpn(1) == 0) return a.Clone(); + + var windowSize = 4; + var wnd = new BN[1 << windowSize]; + wnd[0] = new BN(1).ToRed(this); + wnd[1] = a; + int i; + for (i = 2; i < wnd.Length; i++) + { + wnd[i] = this.Mul(wnd[i - 1], a); + } + + var res = wnd[0]; + var current = 0; + var currentLen = 0; + var start = num.BitLength() % 26; + if (start == 0) + { + start = 26; + } + + for (i = num.Length - 1; i >= 0; i--) + { + var word = num.words[i]; + for (var j = start - 1; j >= 0; j--) + { + var bit = (word >> j) & 1; + if (res != wnd[0]) + { + res = this.Sqr(res); + } + + if (bit == 0 && current == 0) + { + currentLen = 0; + continue; + } + + current <<= 1; + current |= bit; + currentLen++; + if (currentLen != windowSize && (i != 0 || j != 0)) continue; + + res = this.Mul(res, wnd[current]); + currentLen = 0; + current = 0; + } + start = 26; + } + + return res; + } + + public virtual BN ConvertTo(BN num) + { + var r = num.Umod(this.m); + + return r == num ? r.Clone() : r; + } + + public virtual BN ConvertFrom(BN num) + { + var res = num.Clone(); + res.red = null; + return res; + } + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..419e457 --- /dev/null +++ b/README.md @@ -0,0 +1,266 @@ +BNSharp +============ + +![](https://img.shields.io/nuget/v/BNSharp) +![](https://img.shields.io/nuget/dt/BNSharp?color=laim) +![](https://img.shields.io/appveyor/build/XeroXP/bnsharp/master) +![](https://img.shields.io/appveyor/tests/XeroXP/bnsharp/master) + +BigNum in C#. Port of [bn.js](https://github.com/indutny/bn.js/). Public domain. + + +Documentation +============= + +* [Overview](#overview) +* [Installation](#installation) +* [Notation](#notation) + * [Prefixes](#prefixes) + * [Postfixes](#postfixes) + * [Examples](#examples) +* [Instructions](#instructions) + * [Utilities](#utilities) + * [Arithmetics](#arithmetics) + * [Bit operations](#bit-operations) + * [Reduction](#reduction) +* [Fast reduction](#fast-reduction) + * [Reduction context](#reduction-context) + * [Converting numbers](#converting-numbers) + * [Red instructions](#red-instructions) + * [Number Size](#number-size) +* [System requirements](#system-requirements) +* [Development and testing](#development-and-testing) +* [Contributors](#contributors) + + +Overview +-------- + +The primary goal of this project is to produce a translation of bn.js to +C# which is as close as possible to the original implementation. + + +Installation +------------ + +You can install BNSharp via [NuGet](https://www.nuget.org/): + +package manager: + + $ PM> Install-Package BNSharp + +NET CLI: + + $ dotnet add package BNSharp + +or [download source code](../../releases). + + +Notation +-------- + +### Prefixes + +There are several prefixes to instructions that affect the way they work. Here +is the list of them in the order of appearance in the function name: + +* `i` - perform operation in-place, storing the result in the host object (on + which the method was invoked). Might be used to avoid number allocation costs +* `u` - unsigned, ignore the sign of operands when performing operation, or + always return positive value. Second case applies to reduction operations + like `Mod()`. In such cases if the result will be negative - modulo will be + added to the result to make it positive + +### Postfixes + +* `n` - the argument of the function must be a plain JavaScript + Number. Decimals are not supported. +* `rn` - both argument and return value of the function are plain JavaScript + Numbers. Decimals are not supported. + +### Examples + +* `a.Iadd(b)` - perform addition on `a` and `b`, storing the result in `a` +* `a.Umod(b)` - reduce `a` modulo `b`, returning positive value +* `a.Iushln(13)` - shift bits of `a` left by 13 + + +Instructions +----- + +Prefixes/postfixes are put in parens at the end of the line. `endian` - could be +either `LittleEndian` or `BigEndian`. + +### Utilities + +* `a.Clone()` - clone number +* `a.ToString(base, length)` - convert to base-string and pad with zeroes +* `a.ToNumber()` - convert to Number (limited to 53 bits) +* `a.ToJSON()` - convert to JSON compatible hex string (alias of `ToString(16)`) +* `a.ToArray(endian, length)` - convert to byte `Array`, and optionally zero + pad to length, throwing if already exceeding +* `a.BitLength()` - get number of bits occupied +* `a.ZeroBits()` - return number of less-significant consequent zero bits + (example: `1010000` has 4 zero bits) +* `a.ByteLength()` - return number of bytes occupied +* `a.IsNeg()` - true if the number is negative +* `a.IsEven()` - no comments +* `a.IsOdd()` - no comments +* `a.IsZero()` - no comments +* `a.Cmp(b)` - compare numbers and return `-1` (a `<` b), `0` (a `==` b), or `1` (a `>` b) + depending on the comparison result (`Ucmp`, `Cmpn`) +* `a.Lt(b)` - `a` less than `b` (`n`) +* `a.Lte(b)` - `a` less than or equals `b` (`n`) +* `a.Gt(b)` - `a` greater than `b` (`n`) +* `a.Gte(b)` - `a` greater than or equals `b` (`n`) +* `a.Eq(b)` - `a` equals `b` (`n`) +* `a.ToTwos(width)` - convert to two's complement representation, where `width` is bit width +* `a.FromTwos(width)` - convert from two's complement representation, where `width` is the bit width +* `BN.IsBN(object)` - returns true if the supplied `object` is a BN instance +* `BN.Max(a, b)` - return `a` if `a` bigger than `b` +* `BN.Min(a, b)` - return `a` if `a` less than `b` + +### Arithmetics + +* `a.Neg()` - negate sign (`i`) +* `a.Abs()` - absolute value (`i`) +* `a.Add(b)` - addition (`i`, `n`, `in`) +* `a.Sub(b)` - subtraction (`i`, `n`, `in`) +* `a.Mul(b)` - multiply (`i`, `n`, `in`) +* `a.Sqr()` - square (`i`) +* `a.Pow(b)` - raise `a` to the power of `b` +* `a.Div(b)` - divide (`Divn`, `Idivn`) +* `a.Mod(b)` - reduct (`u`, `n`) (but no `Umodn`) +* `a.Divmod(b)` - quotient and modulus obtained by dividing +* `a.DivRound(b)` - rounded division + +### Bit operations + +* `a.Or(b)` - or (`i`, `u`, `iu`) +* `a.And(b)` - and (`i`, `u`, `iu`, `Andln`) (NOTE: `Andln` is going to be replaced + with `Andn` in future) +* `a.Xor(b)` - xor (`i`, `u`, `iu`) +* `a.Setn(b, value)` - set specified bit to `value` +* `a.Shln(b)` - shift left (`i`, `u`, `iu`) +* `a.Shrn(b)` - shift right (`i`, `u`, `iu`) +* `a.Testn(b)` - test if specified bit is set +* `a.Maskn(b)` - clear bits with indexes higher or equal to `b` (`i`) +* `a.Bincn(b)` - add `1 << b` to the number +* `a.Notn(w)` - not (for the width specified by `w`) (`i`) + +### Reduction + +* `a.Gcd(b)` - GCD +* `a.Egcd(b)` - Extended GCD results (`{ A: ..., B: ..., Gcd: ... }`) +* `a.Invm(b)` - inverse `a` modulo `b` + + +Fast reduction +----- + +When doing lots of reductions using the same modulo, it might be beneficial to +use some tricks: like [Montgomery multiplication][0], or using special algorithm +for [Mersenne Prime][1]. + +### Reduction context + +To enable this trick one should create a reduction context: + +```csharp +var red = BN.Red(num); +``` +where `num` is just a BN instance. + +Or: + +```csharp +var red = BN.Red(primeName); +``` + +Where `primeName` is either of these [Mersenne Primes][1]: + +* `'k256'` +* `'p224'` +* `'p192'` +* `'p25519'` + +Or: + +```csharp +var red = BN.Mont(num); +``` + +To reduce numbers with [Montgomery trick][0]. `.Mont()` is generally faster than +`.Red(num)`, but slower than `BN.Red(primeName)`. + +### Converting numbers + +Before performing anything in reduction context - numbers should be converted +to it. Usually, this means that one should: + +* Convert inputs to reducted ones +* Operate on them in reduction context +* Convert outputs back from the reduction context + +Here is how one may convert numbers to `red`: + +```csharp +var redA = a.ToRed(red); +``` +Where `red` is a reduction context created using instructions above + +Here is how to convert them back: + +```csharp +var a = redA.FromRed(); +``` + +### Red instructions + +Most of the instructions from the very start of this readme have their +counterparts in red context: + +* `a.RedAdd(b)`, `a.RedIAdd(b)` +* `a.RedSub(b)`, `a.RedISub(b)` +* `a.RedShl(num)` +* `a.RedMul(b)`, `a.RedIMul(b)` +* `a.RedSqr()`, `a.RedISqr()` +* `a.RedSqrt()` - square root modulo reduction context's prime +* `a.RedInvm()` - modular inverse of the number +* `a.RedNeg()` +* `a.RedPow(b)` - modular exponentiation + +### Number Size + +Optimized for elliptic curves that work with 256-bit numbers. +There is no limitation on the size of the numbers. + + +System requirements +------------------- + +BNSharp supports: + +* Net 6 + + +Development and testing +------------------------ + +Make sure to rebuild projects every time you change code for testing. + +### Testing + +To run tests: + + $ dotnet test + + +Contributors +------------ + +[XeroXP](../../../). + + +[0]: https://en.wikipedia.org/wiki/Montgomery_modular_multiplication +[1]: https://en.wikipedia.org/wiki/Mersenne_prime diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..5bc058d --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,18 @@ +image: Visual Studio 2022 +version: 1.0.{build} +branches: + only: + - master + +configuration: Release + +before_build: + - nuget restore + +build: + verbosity: minimal + project: BNSharp.sln + +test: + assemblies: + - '**\*.Tests.dll' \ No newline at end of file