@@ -1061,6 +1061,236 @@ void property_not_sum_with_original(@ForAll("unsigned1to32") final byte[] a) {
10611061
10621062 // endregion
10631063
1064+ // Simple ADD tests
1065+ // --------------------------------------------------------------------------
1066+ @ Property
1067+ void property_add_matchesBigInteger (
1068+ @ ForAll ("unsigned1to32" ) final byte [] a , @ ForAll ("unsigned1to32" ) final byte [] b ) {
1069+ // Arrange
1070+ final UInt256 ua = UInt256 .fromBytesBE (a );
1071+ final UInt256 ub = UInt256 .fromBytesBE (b );
1072+
1073+ // Act
1074+ final byte [] got = ua .add (ub ).toBytesBE ();
1075+
1076+ // Assert
1077+ BigInteger A = toBigUnsigned (a );
1078+ BigInteger B = toBigUnsigned (b );
1079+ // Addition wraps around at 2^256
1080+ byte [] expected = bigUnsignedToBytes32 (A .add (B ));
1081+ assertThat (got ).containsExactly (expected );
1082+ }
1083+
1084+ @ Property
1085+ void property_add_commutative (
1086+ @ ForAll ("unsigned1to32" ) final byte [] a , @ ForAll ("unsigned1to32" ) final byte [] b ) {
1087+ // Arrange
1088+ final UInt256 ua = UInt256 .fromBytesBE (a );
1089+ final UInt256 ub = UInt256 .fromBytesBE (b );
1090+
1091+ // Act & Assert
1092+ assertThat (ua .add (ub )).isEqualTo (ub .add (ua ));
1093+ }
1094+
1095+ @ Property
1096+ void property_add_associative (
1097+ @ ForAll ("unsigned1to32" ) final byte [] a ,
1098+ @ ForAll ("unsigned1to32" ) final byte [] b ,
1099+ @ ForAll ("unsigned1to32" ) final byte [] c ) {
1100+ // Arrange
1101+ final UInt256 ua = UInt256 .fromBytesBE (a );
1102+ final UInt256 ub = UInt256 .fromBytesBE (b );
1103+ final UInt256 uc = UInt256 .fromBytesBE (c );
1104+
1105+ // Act & Assert
1106+ assertThat (ua .add (ub ).add (uc )).isEqualTo (ua .add (ub .add (uc )));
1107+ }
1108+
1109+ @ Property
1110+ void property_add_identity (@ ForAll ("unsigned1to32" ) final byte [] a ) {
1111+ // Arrange
1112+ final UInt256 ua = UInt256 .fromBytesBE (a );
1113+ final UInt256 zero = UInt256 .ZERO ;
1114+
1115+ // Act & Assert
1116+ assertThat (ua .add (zero )).isEqualTo (ua );
1117+ assertThat (zero .add (ua )).isEqualTo (ua );
1118+ }
1119+
1120+ @ Property
1121+ void property_add_singleLimb_matchesBigInteger (
1122+ @ ForAll ("singleLimbUnsigned1to4" ) final byte [] a ,
1123+ @ ForAll ("singleLimbUnsigned1to4" ) final byte [] b ) {
1124+ // Arrange
1125+ final UInt256 ua = UInt256 .fromBytesBE (a );
1126+ final UInt256 ub = UInt256 .fromBytesBE (b );
1127+
1128+ // Act
1129+ final byte [] got = ua .add (ub ).toBytesBE ();
1130+
1131+ // Assert
1132+ BigInteger A = toBigUnsigned (a );
1133+ BigInteger B = toBigUnsigned (b );
1134+ byte [] expected = bigUnsignedToBytes32 (A .add (B ));
1135+ assertThat (got ).containsExactly (expected );
1136+ }
1137+
1138+ @ Property
1139+ void property_add_one_increment (@ ForAll ("unsigned1to32" ) final byte [] a ) {
1140+ // Arrange
1141+ final UInt256 ua = UInt256 .fromBytesBE (a );
1142+ final UInt256 one = UInt256 .fromBytesBE (new byte [] {1 });
1143+
1144+ // Act
1145+ final byte [] got = ua .add (one ).toBytesBE ();
1146+
1147+ // Assert
1148+ BigInteger A = toBigUnsigned (a );
1149+ byte [] expected = bigUnsignedToBytes32 (A .add (BigInteger .ONE ));
1150+ assertThat (got ).containsExactly (expected );
1151+ }
1152+
1153+ @ Property
1154+ void property_add_self_doubles (@ ForAll ("unsigned1to32" ) final byte [] a ) {
1155+ // Arrange
1156+ final UInt256 ua = UInt256 .fromBytesBE (a );
1157+
1158+ // Act
1159+ final byte [] got = ua .add (ua ).toBytesBE ();
1160+
1161+ // Assert - verify A + A = 2 * A using BigInteger
1162+ BigInteger A = toBigUnsigned (a );
1163+ byte [] expected = bigUnsignedToBytes32 (A .multiply (BigInteger .TWO ));
1164+ assertThat (got ).containsExactly (expected );
1165+ }
1166+
1167+ @ Property
1168+ void property_add_max_values () {
1169+ // Arrange - MAX_UINT256 = 2^256 - 1 (all bits set to 1)
1170+ final UInt256 max =
1171+ UInt256 .fromBytesBE (
1172+ new byte [] {
1173+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1174+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1175+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1176+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1177+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1178+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1179+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1180+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF
1181+ });
1182+ final UInt256 one = UInt256 .fromBytesBE (new byte [] {1 });
1183+ final UInt256 zero = UInt256 .ZERO ;
1184+
1185+ // Act & Assert - MAX + 1 should wrap to 0
1186+ assertThat (max .add (one )).isEqualTo (zero );
1187+
1188+ // MAX + MAX should wrap to MAX - 1 (i.e., 2^256 - 2)
1189+ BigInteger maxBig = BigInteger .ONE .shiftLeft (256 ).subtract (BigInteger .ONE );
1190+ byte [] expMaxPlusMax = bigUnsignedToBytes32 (maxBig .add (maxBig ));
1191+ assertThat (max .add (max ).toBytesBE ()).containsExactly (expMaxPlusMax );
1192+
1193+ // Verify MAX + MAX = 2^256 - 2 (wraps around)
1194+ final UInt256 expectedMaxPlusMax =
1195+ UInt256 .fromBytesBE (
1196+ new byte [] {
1197+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1198+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1199+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1200+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1201+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1202+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1203+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1204+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFE // Last byte is 0xFE
1205+ });
1206+ assertThat (max .add (max )).isEqualTo (expectedMaxPlusMax );
1207+ }
1208+
1209+ @ Property
1210+ void property_add_max_with_random (@ ForAll ("unsigned1to32" ) final byte [] a ) {
1211+ // Arrange - MAX_UINT256
1212+ final UInt256 max =
1213+ UInt256 .fromBytesBE (
1214+ new byte [] {
1215+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1216+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1217+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1218+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1219+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1220+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1221+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1222+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF
1223+ });
1224+ final UInt256 ua = UInt256 .fromBytesBE (a );
1225+
1226+ // Act
1227+ final byte [] got = max .add (ua ).toBytesBE ();
1228+
1229+ // Assert - verify MAX + A wraps correctly
1230+ BigInteger maxBig = BigInteger .ONE .shiftLeft (256 ).subtract (BigInteger .ONE );
1231+ BigInteger A = toBigUnsigned (a );
1232+ byte [] exp = bigUnsignedToBytes32 (maxBig .add (A ));
1233+ assertThat (got ).containsExactly (exp );
1234+
1235+ // Also verify: MAX + A = A - 1 (due to wrapping)
1236+ BigInteger expectedWrapped = A .subtract (BigInteger .ONE );
1237+ if (expectedWrapped .signum () < 0 ) {
1238+ // If A is 0, then MAX + 0 = MAX
1239+ expectedWrapped = maxBig ;
1240+ }
1241+ byte [] expWrapped = bigUnsignedToBytes32 (expectedWrapped );
1242+ assertThat (got ).containsExactly (expWrapped );
1243+ }
1244+
1245+ @ Property
1246+ void property_add_near_max_boundary () {
1247+ // Arrange - test values near the max boundary
1248+ final UInt256 max =
1249+ UInt256 .fromBytesBE (
1250+ new byte [] {
1251+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1252+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1253+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1254+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1255+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1256+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1257+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1258+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF
1259+ });
1260+
1261+ // MAX - 1
1262+ final UInt256 maxMinusOne =
1263+ UInt256 .fromBytesBE (
1264+ new byte [] {
1265+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1266+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1267+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1268+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1269+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1270+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1271+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF ,
1272+ (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFF , (byte ) 0xFE
1273+ });
1274+
1275+ final UInt256 one = UInt256 .fromBytesBE (new byte [] {1 });
1276+ final UInt256 two = UInt256 .fromBytesBE (new byte [] {2 });
1277+
1278+ // Act & Assert
1279+ // (MAX - 1) + 1 = MAX
1280+ assertThat (maxMinusOne .add (one )).isEqualTo (max );
1281+
1282+ // (MAX - 1) + 2 = 0 (wraps)
1283+ assertThat (maxMinusOne .add (two )).isEqualTo (UInt256 .ZERO );
1284+
1285+ // (MAX - 1) + (MAX - 1) should wrap correctly
1286+ BigInteger maxMinusOneBig = BigInteger .ONE .shiftLeft (256 ).subtract (BigInteger .TWO );
1287+ byte [] exp = bigUnsignedToBytes32 (maxMinusOneBig .add (maxMinusOneBig ));
1288+ assertThat (maxMinusOne .add (maxMinusOne ).toBytesBE ()).containsExactly (exp );
1289+ }
1290+
1291+ // --------------------------------------------------------------------------
1292+ // endregion
1293+
10641294 // region Utility Methods
10651295
10661296 private static byte [] clampUnsigned32 (final byte [] any ) {
0 commit comments