@@ -74,27 +74,41 @@ func bsEquallyInitializedint64(s1, s2 *BoundedSumInt64) bool {
7474
7575// BoundedSumInt64Options contains the options necessary to initialize a BoundedSumInt64.
7676type BoundedSumInt64Options struct {
77- Epsilon float64 // Privacy parameter ε. Required.
78- Delta float64 // Privacy parameter δ. Required with Gaussian noise, must be 0 with Laplace noise.
79- MaxPartitionsContributed int64 // How many distinct partitions may a single privacy unit contribute to? Required.
80- // Lower and Upper bounds for clamping. Required; must be such that Lower <= Upper.
77+ Epsilon float64 // Privacy parameter ε. Required.
78+ Delta float64 // Privacy parameter δ. Required with Gaussian noise, must be 0 with Laplace noise.
79+ // How many distinct partitions may a single privacy unit contribute to?
80+ // Mutually exclusive with MaxContributions. Required to be specified along with Lower and Upper when MaxContributions is not set.
81+ MaxPartitionsContributed int64
82+ // Lower and Upper bounds for clamping. Must be such that Lower <= Upper.
83+ // Mutually exclusive with MaxContributions. Required to be specified along with MaxPartitionsContributed when MaxContributions is not set.
8184 Lower , Upper int64
8285 Noise noise.Noise // Type of noise used in BoundedSum. Defaults to Laplace noise.
8386 // How many times may a single privacy unit contribute to a single partition?
8487 // Defaults to 1. This is only needed for other aggregation functions using BoundedSum;
8588 // which is why the option is not exported.
89+ //
90+ // maxContributionsPerPartition is mutually exclusive with MaxContributions. This option has no effect if MaxContributions is set.
8691 maxContributionsPerPartition int64
92+ // How many times may a single privacy unit contribute in total to all partitions?
93+ // Currently only used for Count aggregation function.
94+ //
95+ // Mutually exclusive with set of {MaxPartitionsContributed, Lower, Upper}. Required when {MaxPartitionsContributed, Lower, Upper} are not set.
96+ MaxContributions int64
8797}
8898
8999// NewBoundedSumInt64 returns a new BoundedSumInt64, whose sum is initialized at 0.
90100func NewBoundedSumInt64 (opt * BoundedSumInt64Options ) (* BoundedSumInt64 , error ) {
91101 if opt == nil {
92102 opt = & BoundedSumInt64Options {} // Prevents panicking due to a nil pointer dereference.
93103 }
104+ err := checks .CheckContributionBoundingOptions (opt .MaxContributions , opt .MaxPartitionsContributed )
105+ if err != nil {
106+ return nil , fmt .Errorf ("NewBoundedSumInt64: %w" , err )
107+ }
94108
95- l0 := opt .MaxPartitionsContributed
96- if l0 == 0 {
97- return nil , fmt .Errorf ("NewBoundedSumInt64: MaxPartitionsContributed must be set" )
109+ l0 , err := getL0Int ( opt .MaxContributions , opt . MaxPartitionsContributed )
110+ if err != nil {
111+ return nil , fmt .Errorf ("NewBoundedSumInt64: %w" , err )
98112 }
99113
100114 maxContributionsPerPartition := opt .maxContributionsPerPartition
@@ -108,10 +122,9 @@ func NewBoundedSumInt64(opt *BoundedSumInt64Options) (*BoundedSumInt64, error) {
108122 }
109123 // Check bounds & use them to compute L_∞ sensitivity
110124 lower , upper := opt .Lower , opt .Upper
111- if lower == 0 && upper == 0 {
112- return nil , fmt .Errorf ("NewBoundedSumInt64: Lower and Upper must be set (automatic bounds determination is not implemented yet). Lower and Upper cannot be both 0" )
125+ if opt . MaxPartitionsContributed > 0 && lower == 0 && upper == 0 {
126+ return nil , fmt .Errorf ("NewBoundedSumInt64: When using MaxPartitionsContributed, Lower and Upper must be set (automatic bounds determination is not implemented yet). Lower and Upper cannot be both 0" )
113127 }
114- var err error
115128 switch noise .ToKind (opt .Noise ) {
116129 case noise .Unrecognised :
117130 err = checks .CheckBoundsInt64IgnoreOverflows (lower , upper )
@@ -121,7 +134,20 @@ func NewBoundedSumInt64(opt *BoundedSumInt64Options) (*BoundedSumInt64, error) {
121134 if err != nil {
122135 return nil , fmt .Errorf ("NewBoundedSumInt64: %w" , err )
123136 }
124- lInf , err := getLInfInt (lower , upper , maxContributionsPerPartition )
137+
138+ var lInf int64
139+ if opt .MaxContributions > 0 {
140+ // When using MaxContributions, we set l0=1 and lInf=MaxContributions to represent
141+ // the L1 sensitivity (MaxContributions) in the form expected by the noise layer (l0 * lInf).
142+ // This does not hold for privacy-ID count aggregations, where l0=MaxContributions and lInf=1.
143+ lInf = opt .MaxContributions
144+ // When using MaxContributions, no per-partition contribution bounding is performed.
145+ // upper is set to a default of match.MaxInt64 because it is being used in the Add function below,
146+ // making it a no-op.
147+ upper = math .MaxInt64
148+ } else {
149+ lInf , err = getLInfInt (lower , upper , maxContributionsPerPartition )
150+ }
125151 if err != nil {
126152 if noise .ToKind (opt .Noise ) == noise .Unrecognised {
127153 // Ignore sensitivity overflows if noise is not recognised.
@@ -160,6 +186,16 @@ func lInfIntOverflows(bound, maxContributionsPerPartition int64) bool {
160186 return mult / maxContributionsPerPartition != bound
161187}
162188
189+ func getL0Int (maxContributions , maxPartitionsContributed int64 ) (int64 , error ) {
190+ if err := checks .CheckContributionBoundingOptions (maxContributions , maxPartitionsContributed ); err != nil {
191+ return 0 , err
192+ }
193+ if maxContributions > 0 {
194+ return 1 , nil
195+ }
196+ return maxPartitionsContributed , nil
197+ }
198+
163199// getLInfInt checks that the sensitivity parameters will not create overflow errors,
164200// and returns the L_inf sensitivity of the BoundedSum object, which is calculated by the
165201// formula = max(|lower|, |upper|) * maxContributionsPerPartition.
0 commit comments