|The Fast Fourier Transform,
and a Noisy Signal
|Bezier Curves and Picasso||Computing Homology||Probably Approximately
Correct – A Formal Theory
A while back I announced a preprint of a paper on coloring graphs with certain resilience properties. I’m pleased to announce that it’s been accepted to the Mathematical Foundations of Computer Science 2014, which is being held in Budapest this year. Since we first published the preprint we’ve actually proved some additional results about resilience, and so I’ll expand some of the details here. I think it makes for a nicer overall picture, and in my opinion it gives a little more justification that resilient coloring is interesting, at least in contrast to other resilience problems.
Recall that a “resilient” yes-instance of a combinatorial problem is one which remains a yes-instance when you add or remove some constraints. The way we formalized this for SAT was by fixing variables to arbitrary values. Then the question is how resilient does an instance need to be in order to actually find a certificate for it? In more detail,
Definition: -resilient -SAT formulas are satisfiable formulas in -CNF form (conjunctions of clauses, where each clause is a disjunction of three literals) such that for all choices of variables, every way to fix those variables yields a satisfiable formula.
For example, the following 3-CNF formula is 1-resilient:
The idea is that resilience may impose enough structure on a SAT formula that it becomes easy to tell if it’s satisfiable at all. Unfortunately for SAT (though this is definitely not the case for coloring), there are only two possibilities. Either the instances are so resilient that they never existed in the first place (they’re vacuously trivial), or the instances are NP-hard. The first case is easy: there are no -resilient -SAT formulas. Indeed, if you’re allowed to fix variables to arbitrary values, then you can just pick a clause and set all its variables to false. So no formula can ever remain satisfiable under that condition.
The second case is when the resilience is strictly less than the clause size, i.e. -resilient -SAT for . In this case the problem of finding a satisfying assignment is NP-hard. We’ll show this via a sequence of reductions which start at 3-SAT, and they’ll involve two steps: increasing the clause size and resilience, and decreasing the clause size and resilience. The trick is in balancing which parts are increased and decreased. I call the first step the “blowing up” lemma, and the second part the “shrinking down” lemma.
Blowing Up and Shrinking Down
Here’s the intuition behind the blowing up lemma. If you give me a regular (unresilient) 3-SAT formula , what I can do is make a copy of with a new set of variables and OR the two things together. Call this . This is clearly logically equivalent to the original formula; if you give me a satisfying assignment for the ORed thing, I can just see which of the two clauses are satisfied and use that sub-assignment for , and conversely if you can satisfy it doesn’t matter what truth values you choose for the new set of variables. And further you can transform the ORed formula into a 6-SAT formula in polynomial time. Just apply deMorgan’s rules for distributing OR across AND.
Now the choice of a new set of variables allows us to give some resilient. If you fix one variable to the value of your choice, I can always just work with the other set of variables. Your manipulation doesn’t change the satisfiability of the ORed formula, because I’ve added all of this redundancy. So we took a 3-SAT formula and turned it into a 1-resilient 6-SAT formula.
The idea generalizes to the blowing up lemma, which says that you can measure the effects of a blowup no matter what you start with. More formally, if is the number of copies of variables you make, is the clause size of the starting formula , and is the resilience of , then blowing up gives you an -resilient -SAT formula. The argument is almost identical to the example above the resilience is more general. Specifically, if you fix fewer than variables, then the pigeonhole principle guarantees that one of the copies of variables has at most fixed values, and we can just work with that set of variables (i.e., this small part of the big ORed formula is satisfiable if was -resilient).
The shrinking down lemma is another trick that is similar to the reduction from -SAT to 3-SAT. There you take a clause like and add new variables to break up the clause in to clauses of size 3 as follows:
These are equivalent because your choice of truth values for the tell me which of these sub-clauses to look for a true literal of the old variables. I.e. if you choose then you have to pick either or to be true. And it’s clear that if you’re willing to double the number of variables (a linear blowup) you can always get a -clause down to an AND of 3-clauses.
So the shrinking down reduction does the same thing, except we only split clauses in half. For a clause , call the first half of a clause and the second half (you can see how my Python training corrupts my notation preference). Then to shrink a clause down from size to size (1 for the new variable), add a variable and break into
and just AND these together for all clauses. Call the original formula and the transformed one . The formulas are logically equivalent for the same reason that the -to-3-SAT reduction works, and it’s already in the right CNF form. So resilience is all we have to measure. The claim is that the resilience is , where is the resilience of .
The reason for this is that if all the fixed variables are old variables (not ), then nothing changes and the resilience of the original keeps us safe. And each we fix has no effect except to force us to satisfy a variable in one of the two halves. So there is this implication that if you fix a you have to also fix a regular variable. Because we can’t guarantee anything if we fix more than regular variables, we’d have to stop before fixing of the . And because these new clauses have size , we can’t do this more than times or else we risk ruining an entire clause. So this give the definition of . So this proves the shrinking down lemma.
Resilient SAT is always hard
The blowing up and shrinking down lemmas can be used to show that -resilient -SAT is NP-hard for all . What we do is reduce from 3-SAT to an -resilient -SAT instance in such a way that the 3-SAT formula is satisfiable if and only if the transformed formula is resiliently satisfiable.
What makes these two lemmas work together is that shrinking down shrinks the clause size just barely less than the resilience, and blowing up increases resilience just barely more than it increases clause size. So we can combine these together to climb from 3-SAT up to some high resilience and satisfiability, and then iteratively shrink down until we hit our target.
One might worry that it will take an exponential number of reductions (or a few reductions of exponential size) to get from 3-SAT to the of our choice, but we have a construction that does it in at most four steps, with only a linear initial blowup from 3-SAT to -resilient -SAT. Then, to deal with the odd ceilings and floors in the shrinking down lemma, you have to find a suitable larger to reduce to (by padding with useless variables, which cannot make the problem easier). And you choose this so that you only need at most two applications of shrinking down to get to -resilient -SAT. Our preprint has the gory details (which has an inelegant part that is not worth writing here), but in the end you show that -resilient -SAT is hard, and since that’s the maximal amount of resilience before the problem becomes vacuously trivial, all smaller resilience values are also hard.
So how does this relate to coloring?
I’m happy about this result not just because it answers an open question I’m honestly curious about, but also because it shows that resilient coloring is more interesting. Basically this proves that satisfiability is so hard that no amount of resilience can make it easier in the worst case. But coloring has a gradient of difficulty. Once you get to order resilience for -colorable graphs, the coloring problem can be solved efficiently by a greedy algorithm (and it’s not a vacuously empty class of graphs). Another thing on the side is that we use the hardness of resilient SAT to get the hardness results we have for coloring.
If you really want to stretch the implications, you might argue that this says something like “coloring is somewhat easier than SAT,” because we found a quantifiable axis along which SAT remains difficult while coloring crumbles. The caveat is that fixing colors of vertices is not exactly comparable to fixing values of truth assignments (since we are fixing lots of instances by fixing a variable), but at least it’s something concrete.
Coloring is still mostly open, and recently I’ve been going to talks where people are discussing startlingly similar ideas for things like Hamiltonian cycles. So that makes me happy.
Until next time!
Greedy algorithms are among the simplest and most intuitive algorithms known to humans. Their name essentially gives their description: do the thing that looks best right now, and repeat until nothing looks good anymore or you’re forced to stop. Some of the best situations in computer science are also when greedy algorithms are optimal or near-optimal. There is a beautiful theory of this situation, known as the theory of matroids. We haven’t covered matroids on this blog (at some point we will), but in this post we will focus on the next best thing: when the greedy algorithm guarantees a reasonably good approximation to the optimal solution.
This situation isn’t hard to formalize, and we’ll make it as abstract as possible. Say you have a set of objects , and you’re looking to find the “best” subset . Here “best” is just measured by a fixed (known, efficiently computable) objective function . That is, accepts as input subsets of and outputs numbers so that better subsets have larger numbers. Then the goal is to find a subset maximizing .
In this generality the problem is clearly impossible. You’d have to check all subsets to be sure you didn’t miss the best one. So what conditions do we need on either or or both that makes this problem tractable? There are plenty you could try, but one very rich property is submodularity.
The Submodularity Condition
I think the simplest way to explain submodularity is in terms of coverage. Say you’re starting a new radio show and you have to choose which radio stations to broadcast from to reach the largest number of listeners. For simplicity say each radio station has one tower it broadcasts from, and you have a good estimate of the number of listeners you would reach if you broadcast from a given tower. For more simplicity, say it costs the same to broadcast from each tower, and your budget restricts you to a maximum of ten stations to broadcast from. So the question is: how do you pick towers to maximize your overall reach?
The hidden condition here is that some towers overlap in which listeners they reach. So if you broadcast from two towers in the same city, a listener who has access to both will just pick one or the other. In other words, there’s a diminished benefit to picking two overlapping towers if you already have chosen one.
This “diminishing returns” condition is a general idea you can impose on any function that takes in subsets of a given set and produces numbers. If is a set then for what seems like a strange reason we denote the set of all subsets of by . So we can state this condition more formally,
Definition: Let be a finite set. A function is called submodular if for all subsets and all ,
In other words, if measures “benefit,” then the marginal benefit of adding to is at least as high as the marginal benefit of adding it to . Since and are all arbitrary, this is as general as one could possibly make it.
Before we start doing things with submodular functions, let’s explore some basic properties. The first is an equivalent definition of submodularity
Proposition: is submodular if and only if for all , it holds that
Proof. If we assume has the condition from this proposition, then we can set , and the formula just works out. Conversely, if we have the condition from the definition, then using the fact that we can inductively apply the inequality to each element of to get
Next, we can tweak and combine submodular functions to get more submodular functions. In particular, non-negative linear combinations of sub-modular functions are submodular. In other words, if are submodular on the same set , and are all non-negative reals, then is also a submodular function on . It’s an easy exercise in applying the definition to see why this is true. This is important because when we’re designing objectives to maximize, we can design them by making some simple submodular pieces, and then picking an appropriate combination of those pieces.
The second property we need to impose on a submodular function is monotonicity. That is, as your sets get more elements added to them, their value under only goes up. In other words, is monotone when then . An interesting property of functions that are both submodular and monotone is that the truncation of such a function is also submodular and monotone. In other words, is still submodular when is monotone submodular and is a constant.
Submodularity and Monotonicity Give 1 – 1/e
The wonderful thing about submodular functions is that we have a lot of great algorithmic guarantees for working with them. We’ll prove right now that the coverage problem (while it might be hard to solve in general) can be approximated pretty well by the greedy algorithm.
Here’s the algorithmic setup. I give you a finite set and an efficient black-box to evaluate for any subset you want. I promise you that is monotone and submodular. Now I give you an integer between 1 and the size of , and your task is to quickly find a set of size for which is maximal among all subsets of size . That is, you design an algorithm that will work for any and runs in polynomial time in the sizes of .
In general this problem is NP-hard, meaning you’re not going to find a solution that works in the worst case (if you do, don’t call me; just claim your million dollar prize). So how well can we approximate the optimal value for by a different set of size ? The beauty is that, if your function is monotone and submodular, you can guarantee to get within 63% of the optimum. The hope (and reality) is that in practice it will often perform much better, but still this is pretty good! More formally,
Theorem: Let be a monotone, submodular, non-negative function on . The greedy algorithm, which starts with as the empty set and at every step picks an element which maximizes the marginal benefit , provides a set that achieves a -approximation of the optimum.
We’ll prove this in just a little bit more generality, and the generality is quite useful. If we call the sets chosen by the greedy algorithm (where now we might run the greedy algorithm for steps), then for all , we have
This allows us to run the algorithm for more than steps to get a better approximation by sets of larger size, and quantify how much better the guarantee on that approximation would be. It’s like an algorithmic way of hedging your risk. So let’s prove it.
Proof. Let’s set up some notation first. Fix your and , call the set chosen by the greedy algorithm at step , and call the optimal subset of size . Further call the value of the best set . Call the elements of (the order is irrelevant). Now for every monotonicity gives us . We can unravel this into a sum of marginal gains of adding single elements. The first step is
The second step removes , from the last term, the third removes , and so on until we have removed all of and get this sum
Now, applying submodularity, we can change all of these marginal benefits of “adding one more element to already with some stuff” to “adding one more element to just .” In symbols, the equation above is at most
and because is greedily chosen to maximize the benefit of adding a single element, so the above is at most
Chaining all of these together, we have . If we call , then this inequality can be rewritten as . Now by induction we can relate . Now use the fact that and the common inequality to get
And rearranging gives .
Setting gives the approximation bound we promised. But note that allowing the greedy algorithm to run longer can give much stronger guarantees, though it requires you to sacrifice the cardinality constraint. is about 63%, but doubling the size of gives about an 86% approximation guarantee. This is great for people in the real world, because you can quantify the gains you’d get by relaxing the constraints imposed on you (which are rarely set in stone).
So this is really great! We have quantifiable guarantees on a stupidly simple algorithm, and the setting is super general. And so if you have your problem and you manage to prove your function is submodular (this is often the hardest part), then you are likely to get this nice guarantee.
Extensions and Variations
This result on monotone submodular functions is just one part of a vast literature on finding approximation algorithms for submodular functions in various settings. In closing this post we’ll survey some of the highlights and provide references.
What we did in this post was maximize a monotone submodular function subject to a cardinality constraint . There are three basic variations we could do: we could drop constraints and see whether we can still get guarantees, we could look at minimization instead of maximization, and we could modify the kinds of constraints we impose on the solution.
There are a ton of different kinds of constraints, and we’ll discuss two. The first is where you need to get a certain value , and you want to find the smallest set that achieves this value. Laurence Wolsey (who proved a lot of these theorems) showed in 1982 that a slight variant of the greedy algorithm can achieve a set whose size is a multiplicative factor of worse than the optimum.
The second kind of constraint is a generalization of a cardinality constraint called a knapsack constraint. This means that each item has a cost, and you have a finite budget with which to spend on elements you add to . One might expect this natural extension of the greedy algorithm to work: pick the element which maximizes the ratio of increasing the value of to the cost (within your available budget). Unfortunately this algorithm can perform arbitrarily poorly, but there are two fun caveats. The first is that if you do both this augmented greedy algorithm and the greedy algorithm that ignores costs, then at least one of these can’t do too poorly. Specifically, one of them has to get at least a 30% approximation. This was shown by Leskovec et al in 2007. The second is that if you’re willing to spend more time in your greedy step by choosing the best subset of size 3, then you can get back to the approximation. This was shown by Sviridenko in 2004.
Now we could try dropping the monotonicity constraint. In this setting cardinality constraints are also superfluous, because it could be that the very large sets have low values. Now it turns out that if has no other restrictions (in particular, if it’s allowed to be negative), then even telling whether there’s a set with is NP-hard, but the optimum could be arbitrarily large and positive when it exists. But if you require that is non-negative, then you can get a 1/3-approximation, if you’re willing to add randomness you can get 2/5 in expectation, and with more subtle constraints you can get up to a 1/2 approximation. Anything better is NP-hard. Fiege, Mirrokni, and Vondrak have a nice FOCS paper on this.
Next, we could remove the monotonicity property and try to minimize the value of . It turns out that this problem always has an efficient solution, but the only algorithm I have heard of to solve it involves a very sophisticated technique called the ellipsoid algorithm. This is heavily related to linear programming and convex optimization, something which I hope to cover in more detail on this blog.
Finally, there are many interesting variations in the algorithmic procedure. For example, one could require that the elements are provided in some order (the streaming setting), and you have to pick at each step whether to put the element in your set or not. Alternatively, the objective functions might not be known ahead of time and you have to try to pick elements to jointly maximize them as they are revealed. These two settings have connections to bandit learning problems, which we’ve covered before on this blog. See this survey of Krause and Golovin for more on the connections, which also contains the main proof used in this post.
Indeed, despite the fact that many of the big results were proved in the 80′s, the analysis of submodular functions is still a big research topic. There was even a paper posted just the other day on the arXiv about it’s relation to ad serving! And wouldn’t you know, they proved a -approximation for their setting. There’s juts something about .
Until next time!
I don’t usually write promotional posts because I don’t enjoy reading them as much as I enjoy reading the technical posts. But I know that a lot of early graduate students and undergraduates read my blog, and this would be of interest to many of them.
I just got back from Utah yesterday where I attended a 5-day workshop run by the American Mathematical Society, called the Network Science Mathematical Research Community (MRC).
The point of the program is to bring graduate students and early career folks together from all over the country to start new collaborations. The AMS runs multiple MRC sessions every year, and this year the topics ranged from network science to quantum physics. We had a group of about 20 people, including statisticians, applied mathematicians, computer scientists, and a handful of pure combinatorialists. We self-organized into groups of four, and spent pretty much all day for the next four days eating great food, thinking about problems, proving theorems, enjoying the view, and discussing our ideas with the three extremely smart, successful, and amicable organizers. There were also career panels every evening that were, in my opinion, better than the average career panel.
Anyway, it was a really fun and valuable experience, and the AMS pays for everything and a bag of chips (if by chips you mean more travel money to meet up with your collaborators and a ticket to the AMS Joint Mathematics Meeting the following January). I’m excited to blog about the work that come out of this, as network science is right up there with the coolest of topics in math and programming.
So if you’re eligible, keep an eye out for next year’s program.
Here’s a simple puzzle with a neat story. A rich old woman is drafting her will and wants to distribute her expansive estate equally amongst her five children. But her children are very greedy, and the woman knows that if he leaves her will unprotected her children will resort to nefarious measures to try to get more than their fair share. In one fearful scenario, she worries that the older four children will team up to bully the youngest child entirely out of his claim! She desperately wants them to cooperate, so she decides to lock the will away, and the key is a secret integer . The question is, how can she distribute this secret number to her children so that the only way they can open the safe is if they are all present and willing?
A mathematical way to say this is: how can she distribute some information to her children so that, given all of their separate pieces of information, they can reconstruct the key, but for every choice of fewer than 5 children, there is no way to reliably recover the key? This is called the secret sharing problem. More generally, say we have an integer called the secret, a number of participants , and a number required for reconstruction . Then a secret sharing protocol is the data of a method for distributing information and a method for reconstructing the secret. The distributing method is an algorithm that accepts as input and produces as output a list of numbers . These are the numbers distributed to the participants. Then the reconstruction method is a function which accepts as input numbers and outputs a number . We want two properties to hold :
- The reconstruction function outputs when given any of the numbers output by .
- One cannot reliably reconstruct with fewer than of the numbers output by .
The question is: does an efficient secret sharing protocol exist for every possible choice of ? In fact it does, and the one we’ll describe in this post is far more secure than the word “reliable” suggests. It will be so hard as to be mathematically impossible to reconstruct the secret from fewer than the desired number of pieces. Independently discovered by Adi Shamir in 1979, the protocol we’ll see in this post is wonderfully simple, and as we describe it we’ll build up a program to implement it. This time we’ll work in the Haskell programming language, and you can download the program from this blog’s Github page. And finally, a shout out to my friend Karishma Chadha who worked together with me on this post. She knows Haskell a lot better than I do.
The key to the secret sharing protocol is a beautiful fact about polynomials. Specifically, if you give me points in the plane with distinct values, then there is a unique degree polynomial that passes through the points. Just as importantly (and as a byproduct of this fact), there are infinitely many degree polynomials that pass through the same points. For example, if I give you the points , the only quadratic (degree 2) polynomial that passes through all of them is . The proof that you can always find such a polynomial is pretty painless, so let’s take it slowly and write a program as we go. Suppose you give me some list of points and no two values are the same. The proof has two parts. First we have to prove existence, that some degree polynomial passes through the points, and then we have to prove that the polynomial is unique. The uniqueness part is easier, so let’s do the existence part first. Let’s start with just one point . What’s a degree zero polynomial that passes through it? Just the constant function . For two points it’s similarly easy, since we all probably remember from basic geometry that there’s a unique line passing through any two points. But let’s write the line in a slightly different way:
Why write it this way? Because now it should be obvious that the polynomial passes through our two points: if I plug in then the second term is zero and the first term is just , and likewise for .
For example, if we’re given we get:
Plugging in cancels the second term out, leaving , and plugging in cancels the first term, leaving .
Now the hard step is generalizing this to three points. But the suggestive form above gives us a hint on how to continue.
Notice that the numerators of the terms take on the form , that is, a product excluding . Thus, all terms will cancel out to 0 if we plug in , except one term, which has the form
Here, the fraction on the right side of the term cancels out to 1 when is plugged in, leaving only , the desired result. Now that we’ve written the terms in this general product form, we can easily construct examples for any number of points. We just do a sum of terms that look like this, one for each value. Try writing this out as a summation, if you feel comfortable with notation.
Let’s go further and write an algorithm to construct the polynomial for us. Some preliminaries: we encode a polynomial as a list of coefficients in degree-increasing order, so that is represented by
type Point = (Rational, Rational) type Polynomial = [Rational] --Polynomials are represented in ascending degree order
Then we can write some simple functions for adding and multiplying polynomials
addPoly :: Polynomial -> Polynomial -> Polynomial addPoly   =  addPoly  xs = xs addPoly xs  = xs addPoly (x:xs) (y:ys) = (x+y) : (addPoly xs ys) multNShift :: Polynomial -> (Rational, Int) -> Polynomial multNShift xs (y, shift) = (replicate shift 0) ++ ( map ((*) y) xs) multPoly :: Polynomial -> Polynomial -> Polynomial multPoly   =  multPoly  _ =  multPoly _  =  multPoly xs ys = foldr addPoly  $ map (multNShift ys) $ zip xs [0..]
multNShift multiplies a polynomial by a monomial (like ), and
multPoly does the usual distribution of terms, using multNShift to do most of the hard work. Then to construct the polynomial we need one more helper function to extract all elements of a list except a specific entry:
allBut :: Integer -> [a] -> [a] allBut i list = snd $ unzip $ filter (\ (index,_) -> i /= index) $ zip [0..] list
And now we can construct a polynomial from a list of points in the same way we did mathematically.
findPolynomial :: [Point] -> Polynomial findPolynomial points = let term (i, (xi,yi)) = let prodTerms = map (\ (xj, _) -> [-xj/(xi - xj), 1/(xi - xj)]) $ allBut i points in multPoly [yi] $ foldl multPoly  prodTerms in foldl addPoly  $ map term $ zip [0..] points
Here the sub-function
term constructs the -th term of the polynomial, and the remaining expression adds up all the terms. Remember that due to our choice of representation the awkward 1 sitting in the formula signifies the presence of . And that’s it! An example of it’s use to construct :
*Main> findPolynomial [(1,2), (2,5)] [(-1) % 1,3 % 1]
Now the last thing we need to do is show that the polynomial we constructed in this way is unique. Here’s a proof.
Suppose there are two degree polynomials and that pass through the given data points . Let , and we want to show that is the zero polynomial. This proves that is unique because the only assumptions we made at the beginning were that both passed through the given points. Now since both and are degree polynomials, is a polynomial of degree at most . It is also true that where . Thus, we have (at least) roots of this degree polynomial. But this can’t happen by the fundamental theorem of algebra! In more detail: if a nonzero degree polynomial really could have distinct roots, then you could factor it into at least linear terms like . But since there are copies of , would need to be a degree polynomial! The only way to resolve this contradiction is if is actually the zero polynomial, and thus , .
This completes the proof. Now that we know these polynomials exist and are unique, it makes sense to give them a name. So for a given set of points, call the unique degree polynomial that passes through them the interpolating polynomial for those points.
Secret Sharing with Interpolating Polynomials
Once you think to use interpolating polynomials, the connection to secret sharing seems almost obvious. If you want to distribute a secret to people so that of them can reconstruct it here’s what you do:
- Pick a random polynomial of degree so that the secret is .
- Distribute the points .
Then the reconstruction function is: take the points provided by at least participants, use them to reconstruct , and output . That’s it! Step 1 might seem hard at first, but you can just notice that is equivalent to the constant term of the polynomial, so you can pick random numbers for the other coefficients of and output them. In Haskell,
makePolynomial :: Rational -> Int -> StdGen -> Polynomial makePolynomial secret r generator = secret : map toRational (take (r-1) $ randomRs (1, (numerator(2*secret))) generator) share :: Rational -> Integer -> Int -> IO [Point] share secret k r = do generator <- getStdGen let poly = makePolynomial secret r generator ys = map (eval poly) $ map toRational [1..k] return $ zip [1..] ys
In words, we initialize the Haskell standard generator (which wraps the results inside an IO monad), then we construct a polynomial by letting the first coefficient be the secret and choosing random coefficients for the rest. And
findPolynomial is the reconstruction function.
Finally, just to flush the program out a little more, we write a function that encodes or decodes a string as an integer.
encode :: String -> Integer encode str = let nums = zip [0..] $ map (toInteger . ord) str integers = map (\(i, n) -> shift n (i*8)) nums in foldl (+) 0 integers decode :: Integer -> String decode 0 = "" decode num = if num < 0 then error "Can't decode a negative number" else chr (fromInteger (num .&. 127)) : (decode $ shift num (-8))
And then we have a function that shows the whole process in action.
example msg k r = let secret = toRational $ encode msg in do points (numerator x, numerator y)) points let subset = take r points encodedSecret = eval (findPolynomial subset) 0 putStrLn $ show $ numerator encodedSecret putStrLn $ decode $ numerator encodedSecret
And a function call:
*Main> example "Hello world!" 10 5 10334410032606748633331426632 [(1,34613972928232668944107982702),(2,142596447049264820443250256658),(3,406048862884360219576198642966),(4,916237517700482382735379150124),(5,1783927975542901326260203400662),(6,3139385067235193566437068631142),(7,5132372890379242119499357692158),(8,7932154809355236501627439048336),(9,11727493455321672728948666778334),(10,16726650726215353317537380574842)] 10334410032606748633331426632 Hello world!
The final question to really close this problem with a nice solution is, “How secure is this protocol?” That is, if you didn’t know the secret but you had numbers, could you find a way to recover the secret, oh, say, 0.01% of the time?
Pleasingly, the answer is a solid no. This protocol has something way stronger, what’s called information-theoretic security. In layman’s terms, this means it cannot possibly be broken, period. That is, without taking advantage of some aspect of the random number generator, which we assume is a secure random number generator. But with that assumption the security proof is trivial. Here it goes.
Pick a number that isn’t the secret . It’s any number you want. And say you only have of the correct numbers . Then there is a final number so that the protocol reconstructs instead of . This is no matter which of the unused -values you pick, no matter what and numbers you started with. This is simply because adding in defines a new polynomial , and you can use any point on as your -th number.
Here’s what this means. A person trying to break the secret sharing protocol would have no way to tell if they did it correctly! If the secret is a message, then a bad reconstruction could produce any message. In information theory terms, knowing of the numbers provides no information about the actual message. In our story from the beginning of the post, no matter how much computing power one of the greedy children may have, the only algorithm they have to open the safe is to try every combination. The mother could make the combination have length in the millions of digits, or even better, the mother could encode the will as an integer and distribute that as the secret. I imagine there are some authenticity issues there, since one could claim to have reconstructed a false will, signatures and all, but there appear to be measures to account for this.
One might wonder if this is the only known secret sharing protocol, and the answer is no. Essentially, any time you have an existence and uniqueness theorem in mathematics, and the objects you’re working with are efficiently constructible, then you have the potential for a secret sharing protocol. There are two more on Wikipedia. But people don’t really care to find new ones anymore because the known protocols are as good as it gets.
On a broader level, the existence of efficient secret sharing protocols is an important fact used in the field of secure multiparty computation. Here the goal is for a group of individuals to compute a function depending on secret information from all of them, without revealing their secret information to anyone. A classic example of this is to compute the average of seven salaries without revealing any of the salaries. This was a puzzle featured on Car Talk, and it has a cute answer. See if you can figure it out.
Until next time!