├── .github └── workflows │ └── test.yml ├── .gitignore ├── README.md ├── eea ├── eea.go ├── eea_suite_test.go ├── eea_test.go ├── eeautil │ └── eeautil.go ├── marshal.go └── marshal_test.go ├── go.mod ├── go.sum ├── marshal.go ├── marshal_test.go ├── poly ├── interpolator.go ├── interpolator_test.go ├── marshal.go ├── marshal_test.go ├── poly.go ├── poly_suite_test.go ├── poly_test.go └── polyutil │ └── polyutil.go ├── rs ├── marshal.go ├── marshal_test.go ├── rs.go ├── rs_suite_test.go └── rs_test.go ├── shamir.go ├── shamir_suite_test.go ├── shamir_test.go ├── shamirutil ├── misc.go └── shamir.go ├── vss.go └── vss_test.go /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-go@v2 9 | with: 10 | go-version: "^1.14.0" 11 | - uses: actions/cache@v1 12 | with: 13 | path: ~/go/pkg/mod 14 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 15 | restore-keys: | 16 | ${{ runner.os }}-go- 17 | - name: Run vetting 18 | run: | 19 | cd $GITHUB_WORKSPACE 20 | export PATH=$PATH:$(go env GOPATH)/bin 21 | cd $GITHUB_WORKSPACE 22 | go vet ./... 23 | - name: Run linting 24 | run: | 25 | cd $GITHUB_WORKSPACE 26 | export PATH=$PATH:$(go env GOPATH)/bin 27 | go get -u golang.org/x/lint/golint 28 | go vet ./... 29 | golint ./... 30 | - name: Run tests 31 | env: 32 | # COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }} 33 | CI: true 34 | run: | 35 | cd $GITHUB_WORKSPACE 36 | export PATH=$PATH:$(go env GOPATH)/bin 37 | # go get -u github.com/mattn/goveralls 38 | go test --race --cover --coverprofile shamir.coverprofile ./... 39 | # goveralls -coverprofile=shamir.coverprofile -service=circleci -repotoken $COVERALLS_TOKEN 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/prof/* 2 | **/*.test 3 | 4 | # Cover and cpu profiles 5 | **/*.out 6 | 7 | # Benchmarks 8 | **/*.bench 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shamir 2 | 3 | [![GitHub](https://github.com/renproject/shamir/workflows/test/badge.svg)](https://github.com/renproject/shamir/workflows/test/badge.svg) 4 | 5 | Types and methods for Shamir secret sharing, as well as Pedersen verifiable 6 | secret sharing. 7 | -------------------------------------------------------------------------------- /eea/eea.go: -------------------------------------------------------------------------------- 1 | package eea 2 | 3 | import ( 4 | "github.com/renproject/shamir/poly" 5 | ) 6 | 7 | // Stepper encapsulates the functionality of the Extended Euclidean Algorithm. 8 | // It holds the internal state of the algorithm, and allows it to be stepped, 9 | // and hence allows this state to be inspected at points in the algorithm 10 | // before the canonical termination condition. 11 | type Stepper struct { 12 | rPrev, rNext poly.Poly 13 | sPrev, sNext poly.Poly 14 | tPrev, tNext poly.Poly 15 | q, r poly.Poly 16 | } 17 | 18 | // NewStepperWithCapacity constructs a new EEA algorithm object with the given 19 | // capacity. The capacity should be at least as large as the capacity of the 20 | // largerst polynomial that the EEA is to be performed with, otherwise 21 | // executing the algorithm may cause a panic. 22 | func NewStepperWithCapacity(c int) Stepper { 23 | rPrev, rNext := poly.NewWithCapacity(c), poly.NewWithCapacity(c) 24 | sPrev, sNext := poly.NewWithCapacity(c), poly.NewWithCapacity(c) 25 | tPrev, tNext := poly.NewWithCapacity(c), poly.NewWithCapacity(c) 26 | q, r := poly.NewWithCapacity(c), poly.NewWithCapacity(c) 27 | 28 | return Stepper{ 29 | rPrev, rNext, 30 | sPrev, sNext, 31 | tPrev, tNext, 32 | q, r, 33 | } 34 | } 35 | 36 | // Rem returns a reference to the current remainder term for the EEA. 37 | func (eea *Stepper) Rem() *poly.Poly { 38 | return &eea.rNext 39 | } 40 | 41 | // S returns a reference to the current s term for the EEA. 42 | func (eea *Stepper) S() *poly.Poly { 43 | return &eea.sNext 44 | } 45 | 46 | // T returns a reference to the current t term for the EEA. 47 | func (eea *Stepper) T() *poly.Poly { 48 | return &eea.tNext 49 | } 50 | 51 | // Init performs the initialisation of the state for the EEA for the given 52 | // input polynomials. No steps in the algorithm are performed. 53 | func (eea *Stepper) Init(a, b poly.Poly) { 54 | // r0 = a, r1 = b, 55 | eea.rPrev.Set(a) 56 | eea.rNext.Set(b) 57 | // s0 = 1, s1 = 0, 58 | eea.sPrev.Zero() 59 | eea.sPrev.Coefficient(0).SetU16(1) 60 | eea.sNext.Zero() 61 | // t0 = 0, t1 = 1 62 | eea.tPrev.Zero() 63 | eea.tNext.Zero() 64 | eea.tNext.Coefficient(0).SetU16(1) 65 | } 66 | 67 | // Step carries out one step of the EEA. It returns a boolean that is true when 68 | // the state has reached the canonical termination condition (r_{k+1} = 0). 69 | func (eea *Stepper) Step() bool { 70 | poly.Divide(eea.rPrev, eea.rNext, &eea.q, &eea.r) 71 | 72 | eea.rPrev.Set(eea.rNext) 73 | eea.rNext.Set(eea.r) 74 | 75 | // sNext, sPrev = sPrev - q * sNext, sNext 76 | eea.r.Mul(eea.q, eea.sNext) 77 | eea.r.Sub(eea.sPrev, eea.r) 78 | eea.sPrev.Set(eea.sNext) 79 | eea.sNext.Set(eea.r) 80 | 81 | // tNext, tPrev = tPrev - q * tNext, tNext 82 | eea.r.Mul(eea.q, eea.tNext) 83 | eea.r.Sub(eea.tPrev, eea.r) 84 | eea.tPrev.Set(eea.tNext) 85 | eea.tNext.Set(eea.r) 86 | 87 | return eea.rNext.IsZero() 88 | } 89 | -------------------------------------------------------------------------------- /eea/eea_suite_test.go: -------------------------------------------------------------------------------- 1 | package eea_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestEea(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Eea Suite") 13 | } 14 | -------------------------------------------------------------------------------- /eea/eea_test.go: -------------------------------------------------------------------------------- 1 | package eea_test 2 | 3 | import ( 4 | "math/rand" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | . "github.com/renproject/shamir/eea" 9 | 10 | "github.com/renproject/shamir/poly" 11 | "github.com/renproject/shamir/poly/polyutil" 12 | ) 13 | 14 | var _ = Describe("Extended Euclidean Algorithm", func() { 15 | Context("when running the extended euclidean algorithm", func() { 16 | Specify("in each step it should satisfy the invariant relation", func() { 17 | trials := 1000 18 | maxDegree := 20 19 | 20 | var degreeA, degreeB int 21 | 22 | a := poly.NewWithCapacity(maxDegree + 1) 23 | b := poly.NewWithCapacity(maxDegree + 1) 24 | temp1 := poly.NewWithCapacity(2 * (maxDegree + 1)) 25 | temp2 := poly.NewWithCapacity(2 * (maxDegree + 1)) 26 | rem := poly.NewWithCapacity(2 * (maxDegree + 1)) 27 | eea := NewStepperWithCapacity(maxDegree + 1) 28 | 29 | for i := 0; i < trials; i++ { 30 | degreeA = rand.Intn(maxDegree + 1) 31 | degreeB = rand.Intn(maxDegree + 1) 32 | polyutil.SetRandomPolynomial(&a, degreeA) 33 | polyutil.SetRandomPolynomial(&b, degreeB) 34 | if b.IsZero() { 35 | continue 36 | } 37 | eea.Init(a, b) 38 | 39 | temp1.Mul(a, *eea.S()) 40 | temp2.Mul(b, *eea.T()) 41 | rem.Add(temp1, temp2) 42 | Expect(rem.Eq(*eea.Rem())).To(BeTrue()) 43 | 44 | for !eea.Step() { 45 | temp1.Mul(a, *eea.S()) 46 | temp2.Mul(b, *eea.T()) 47 | rem.Add(temp1, temp2) 48 | Expect(rem.Eq(*eea.Rem())).To(BeTrue()) 49 | } 50 | } 51 | }) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /eea/eeautil/eeautil.go: -------------------------------------------------------------------------------- 1 | package eeautil 2 | 3 | import "math/rand" 4 | 5 | // Contains returns true if the given slice contains the given int. 6 | func Contains(list []int, ind int) bool { 7 | for _, v := range list { 8 | if ind == v { 9 | return true 10 | } 11 | } 12 | return false 13 | } 14 | 15 | // RandomSubset sets the destintation slice to be a random subset of the 16 | // numbers 0, ..., l of size n. 17 | func RandomSubset(dst *[]int, n, l int) { 18 | *dst = (*dst)[:0] 19 | for i := 0; i < n; i++ { 20 | ind := rand.Intn(l) 21 | 22 | for Contains(*dst, ind) { 23 | if ind == l-1 { 24 | ind = 0 25 | } else { 26 | ind++ 27 | } 28 | } 29 | 30 | *dst = append(*dst, ind) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /eea/marshal.go: -------------------------------------------------------------------------------- 1 | package eea 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/shamir/poly" 8 | ) 9 | 10 | // Generate implements the quick.Generator interface. 11 | func (eea Stepper) Generate(rand *rand.Rand, size int) reflect.Value { 12 | size = size / 8 13 | rPrev := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 14 | rNext := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 15 | sPrev := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 16 | sNext := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 17 | tPrev := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 18 | tNext := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 19 | q := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 20 | r := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 21 | stepper := Stepper{ 22 | rPrev: rPrev, rNext: rNext, 23 | sPrev: sPrev, sNext: sNext, 24 | tPrev: tPrev, tNext: tNext, 25 | q: q, r: r, 26 | } 27 | return reflect.ValueOf(stepper) 28 | } 29 | 30 | // SizeHint implements the surge.SizeHinter interface. 31 | func (eea Stepper) SizeHint() int { 32 | return eea.rNext.SizeHint() + 33 | eea.rPrev.SizeHint() + 34 | eea.sNext.SizeHint() + 35 | eea.sPrev.SizeHint() + 36 | eea.tNext.SizeHint() + 37 | eea.tPrev.SizeHint() + 38 | eea.q.SizeHint() + 39 | eea.r.SizeHint() 40 | } 41 | 42 | // Marshal implements the surge.Marshaler interface. 43 | func (eea Stepper) Marshal(buf []byte, rem int) ([]byte, int, error) { 44 | buf, rem, err := eea.rNext.Marshal(buf, rem) 45 | if err != nil { 46 | return buf, rem, err 47 | } 48 | buf, rem, err = eea.rPrev.Marshal(buf, rem) 49 | if err != nil { 50 | return buf, rem, err 51 | } 52 | buf, rem, err = eea.sNext.Marshal(buf, rem) 53 | if err != nil { 54 | return buf, rem, err 55 | } 56 | buf, rem, err = eea.sPrev.Marshal(buf, rem) 57 | if err != nil { 58 | return buf, rem, err 59 | } 60 | buf, rem, err = eea.tNext.Marshal(buf, rem) 61 | if err != nil { 62 | return buf, rem, err 63 | } 64 | buf, rem, err = eea.tPrev.Marshal(buf, rem) 65 | if err != nil { 66 | return buf, rem, err 67 | } 68 | buf, rem, err = eea.q.Marshal(buf, rem) 69 | if err != nil { 70 | return buf, rem, err 71 | } 72 | return eea.r.Marshal(buf, rem) 73 | } 74 | 75 | // Unmarshal implements the surge.Unmarshaler interface. 76 | func (eea *Stepper) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 77 | buf, rem, err := eea.rNext.Unmarshal(buf, rem) 78 | if err != nil { 79 | return buf, rem, err 80 | } 81 | buf, rem, err = eea.rPrev.Unmarshal(buf, rem) 82 | if err != nil { 83 | return buf, rem, err 84 | } 85 | buf, rem, err = eea.sNext.Unmarshal(buf, rem) 86 | if err != nil { 87 | return buf, rem, err 88 | } 89 | buf, rem, err = eea.sPrev.Unmarshal(buf, rem) 90 | if err != nil { 91 | return buf, rem, err 92 | } 93 | buf, rem, err = eea.tNext.Unmarshal(buf, rem) 94 | if err != nil { 95 | return buf, rem, err 96 | } 97 | buf, rem, err = eea.tPrev.Unmarshal(buf, rem) 98 | if err != nil { 99 | return buf, rem, err 100 | } 101 | buf, rem, err = eea.q.Unmarshal(buf, rem) 102 | if err != nil { 103 | return buf, rem, err 104 | } 105 | return eea.r.Unmarshal(buf, rem) 106 | } 107 | -------------------------------------------------------------------------------- /eea/marshal_test.go: -------------------------------------------------------------------------------- 1 | package eea_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/renproject/surge/surgeutil" 8 | 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | . "github.com/renproject/shamir/eea" 12 | ) 13 | 14 | var _ = Describe("Surge marshalling", func() { 15 | trials := 100 16 | t := reflect.TypeOf(Stepper{}) 17 | 18 | Context(fmt.Sprintf("surge marshalling and unmarshalling for %v", t), func() { 19 | It("should be the same after marshalling and unmarshalling", func() { 20 | for i := 0; i < trials; i++ { 21 | Expect(surgeutil.MarshalUnmarshalCheck(t)).To(Succeed()) 22 | } 23 | }) 24 | 25 | It("should not panic when fuzzing", func() { 26 | for i := 0; i < trials; i++ { 27 | Expect(func() { surgeutil.Fuzz(t) }).ToNot(Panic()) 28 | } 29 | }) 30 | 31 | Context("marshalling", func() { 32 | It("should return an error when the buffer is too small", func() { 33 | for i := 0; i < trials; i++ { 34 | Expect(surgeutil.MarshalBufTooSmall(t)).To(Succeed()) 35 | } 36 | }) 37 | 38 | It("should return an error when the memory quota is too small", func() { 39 | for i := 0; i < trials; i++ { 40 | Expect(surgeutil.MarshalRemTooSmall(t)).To(Succeed()) 41 | } 42 | }) 43 | }) 44 | 45 | Context("unmarshalling", func() { 46 | It("should return an error when the buffer is too small", func() { 47 | for i := 0; i < trials; i++ { 48 | Expect(surgeutil.UnmarshalBufTooSmall(t)).To(Succeed()) 49 | } 50 | }) 51 | 52 | It("should return an error when the memory quota is too small", func() { 53 | for i := 0; i < trials; i++ { 54 | Expect(surgeutil.UnmarshalRemTooSmall(t)).To(Succeed()) 55 | } 56 | }) 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/renproject/shamir 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/onsi/ginkgo v1.16.5 7 | github.com/onsi/gomega v1.19.0 8 | github.com/renproject/secp256k1 v0.0.0-20220707021023-f849b5f8a3c6 9 | github.com/renproject/surge v1.2.7 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 2 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 3 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 4 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM= 8 | github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564/go.mod h1:yekO+3ZShy19S+bsmnERmznGy9Rfg6dWWWpiGJjNAz8= 9 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 10 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 11 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= 12 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 13 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 14 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 15 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 16 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 17 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 18 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 19 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 20 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 21 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 22 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 23 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 24 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 25 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 26 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 27 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 28 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 30 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 31 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 32 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 33 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 34 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 35 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 36 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 37 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 38 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 39 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 40 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 41 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 42 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 43 | github.com/onsi/ginkgo v1.12.3/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 44 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 45 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 46 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 47 | github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= 48 | github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= 49 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 50 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 51 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 52 | github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= 53 | github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= 54 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 55 | github.com/renproject/secp256k1 v0.0.0-20220707021023-f849b5f8a3c6 h1:Hf4TToOy3apEZ3jWN6l3bm3fkMUMYLdoJb66E2ouRPE= 56 | github.com/renproject/secp256k1 v0.0.0-20220707021023-f849b5f8a3c6/go.mod h1:Era4/eG42Iwo4VU/AP3h7WBSONOazmvmtuOntwAbAsc= 57 | github.com/renproject/surge v1.2.3/go.mod h1:jNVsKCM3/2PAllkc2cx7g2saG9NrHRX5x20I/TDMXOs= 58 | github.com/renproject/surge v1.2.7 h1:dooK41jCKENv7Sw/evH1zl6+1xN105wH14sPGAag0ZA= 59 | github.com/renproject/surge v1.2.7/go.mod h1:jKRy1o6KtmDPIcb5g8iwyEZfcjlFUksNqhs9sy6NSRU= 60 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 61 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 62 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 63 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 64 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 65 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 66 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 67 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 68 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 69 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 70 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 71 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 72 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 73 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 74 | golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= 75 | golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 76 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 77 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 78 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 79 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 80 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 81 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 82 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 83 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 84 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 85 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 86 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 87 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 88 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 89 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 90 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 91 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 92 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 93 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 94 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 95 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 96 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo= 97 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 98 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 99 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 100 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 101 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 102 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 103 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 104 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 105 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 106 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 107 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 108 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 109 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 110 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 111 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 112 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 113 | golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= 114 | golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= 115 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 116 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 117 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 118 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 119 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 120 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 121 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 122 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 123 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 124 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 125 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 126 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 127 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 128 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 129 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 130 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 131 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 132 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 133 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 134 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 135 | -------------------------------------------------------------------------------- /marshal.go: -------------------------------------------------------------------------------- 1 | package shamir 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/secp256k1" 8 | "github.com/renproject/surge" 9 | ) 10 | 11 | // Generate implements the quick.Generator interface. 12 | func (s Share) Generate(_ *rand.Rand, _ int) reflect.Value { 13 | return reflect.ValueOf(NewShare(secp256k1.RandomFn(), secp256k1.RandomFn())) 14 | } 15 | 16 | // SizeHint implements the surge.SizeHinter interface. 17 | func (s Share) SizeHint() int { return s.Index.SizeHint() + s.Value.SizeHint() } 18 | 19 | // Marshal implements the surge.Marshaler interface. 20 | func (s Share) Marshal(buf []byte, rem int) ([]byte, int, error) { 21 | buf, rem, err := s.Index.Marshal(buf, rem) 22 | if err != nil { 23 | return buf, rem, err 24 | } 25 | 26 | return s.Value.Marshal(buf, rem) 27 | } 28 | 29 | // Unmarshal implements the surge.Unmarshaler interface. 30 | func (s *Share) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 31 | buf, rem, err := s.Index.Unmarshal(buf, rem) 32 | if err != nil { 33 | return buf, rem, err 34 | } 35 | 36 | return s.Value.Unmarshal(buf, rem) 37 | } 38 | 39 | // SizeHint implements the surge.SizeHinter interface. 40 | func (shares Shares) SizeHint() int { return surge.SizeHintU32 + ShareSize*len(shares) } 41 | 42 | // Marshal implements the surge.Marshaler interface. 43 | func (shares Shares) Marshal(buf []byte, rem int) ([]byte, int, error) { 44 | buf, rem, err := surge.MarshalU32(uint32(len(shares)), buf, rem) 45 | if err != nil { 46 | return buf, rem, err 47 | } 48 | 49 | for i := range shares { 50 | buf, rem, err = shares[i].Marshal(buf, rem) 51 | if err != nil { 52 | return buf, rem, err 53 | } 54 | } 55 | 56 | return buf, rem, nil 57 | } 58 | 59 | // Unmarshal implements the surge.Unmarshaler interface. 60 | func (shares *Shares) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 61 | var l uint32 62 | buf, rem, err := surge.UnmarshalLen(&l, ShareSize, buf, rem) 63 | if err != nil { 64 | return buf, rem, err 65 | } 66 | 67 | if *shares == nil { 68 | *shares = make(Shares, 0, l) 69 | } 70 | 71 | *shares = (*shares)[:0] 72 | for i := uint32(0); i < l; i++ { 73 | *shares = append(*shares, Share{}) 74 | buf, rem, err = (*shares)[i].Unmarshal(buf, rem) 75 | if err != nil { 76 | return buf, rem, err 77 | } 78 | } 79 | return buf, rem, nil 80 | } 81 | -------------------------------------------------------------------------------- /marshal_test.go: -------------------------------------------------------------------------------- 1 | package shamir_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/renproject/surge/surgeutil" 8 | 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | . "github.com/renproject/shamir" 12 | ) 13 | 14 | var _ = Describe("Surge marshalling", func() { 15 | trials := 100 16 | types := []reflect.Type{ 17 | reflect.TypeOf(Share{}), 18 | reflect.TypeOf(Shares{}), 19 | reflect.TypeOf(Commitment{}), 20 | reflect.TypeOf(VerifiableShare{}), 21 | reflect.TypeOf(VerifiableShares{}), 22 | } 23 | 24 | for _, t := range types { 25 | t := t 26 | 27 | Context(fmt.Sprintf("surge marshalling and unmarshalling for %v", t), func() { 28 | It("should be the same after marshalling and unmarshalling", func() { 29 | for i := 0; i < trials; i++ { 30 | Expect(surgeutil.MarshalUnmarshalCheck(t)).To(Succeed()) 31 | } 32 | }) 33 | 34 | It("should not panic when fuzzing", func() { 35 | for i := 0; i < trials; i++ { 36 | Expect(func() { surgeutil.Fuzz(t) }).ToNot(Panic()) 37 | } 38 | }) 39 | 40 | Context("marshalling", func() { 41 | It("should return an error when the buffer is too small", func() { 42 | for i := 0; i < trials; i++ { 43 | Expect(surgeutil.MarshalBufTooSmall(t)).To(Succeed()) 44 | } 45 | }) 46 | 47 | It("should return an error when the memory quota is too small", func() { 48 | for i := 0; i < trials; i++ { 49 | Expect(surgeutil.MarshalRemTooSmall(t)).To(Succeed()) 50 | } 51 | }) 52 | }) 53 | 54 | Context("unmarshalling", func() { 55 | It("should return an error when the buffer is too small", func() { 56 | for i := 0; i < trials; i++ { 57 | Expect(surgeutil.UnmarshalBufTooSmall(t)).To(Succeed()) 58 | } 59 | }) 60 | 61 | It("should return an error when the memory quota is too small", func() { 62 | for i := 0; i < trials; i++ { 63 | Expect(surgeutil.UnmarshalRemTooSmall(t)).To(Succeed()) 64 | } 65 | }) 66 | }) 67 | }) 68 | } 69 | }) 70 | -------------------------------------------------------------------------------- /poly/interpolator.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "github.com/renproject/secp256k1" 5 | ) 6 | 7 | // Interpolator can perform polynomial interpolation. That is, the act of 8 | // taking a set of points on a polynomial and finding a polynomial that passes 9 | // through all of those points. This is encapsulated in an object because when 10 | // interpolating multiple sets of points, all of which have the same set of 11 | // corresponding x coordinates, each interpolation can use the same setup, 12 | // improving efficiency. 13 | type Interpolator struct { 14 | basis []Poly 15 | } 16 | 17 | // NewInterpolator constructs a new polynomial interpolator for the given set 18 | // of indices. The indices represent the x coordinates of the points that will 19 | // be interpolated. That is, if the set of indices is `{x0, x1, ..., xn}`, then 20 | // the constructed interpolator will be able to interpolate any set of points 21 | // of the form `{(x0, y0), (x1, y1), ..., (xn, yn)}` for any `y0, y1, ..., yn`. 22 | func NewInterpolator(indices []secp256k1.Fn) Interpolator { 23 | // Interpolation will use Lagrange polynomial interpolation 24 | 25 | // One basis polynomial for each index 26 | basis := make([]Poly, len(indices)) 27 | for i := range basis { 28 | // Each basis polynomial has degree equal to the number of indices 29 | // minus one 30 | basis[i] = NewWithCapacity(len(indices)) 31 | } 32 | 33 | numerator := NewWithCapacity(2) 34 | var denominator secp256k1.Fn 35 | 36 | // Compute basis polynomials 37 | numerator = numerator[:2] 38 | for i := range basis { 39 | basis[i][0].SetU16(1) 40 | 41 | for j := range indices { 42 | if i == j { 43 | continue 44 | } 45 | 46 | // Numerator x - xj 47 | numerator[0].Negate(&indices[j]) 48 | numerator[1].SetU16(1) 49 | 50 | // Denominator xi - xj 51 | denominator.Negate(&indices[j]) 52 | denominator.Add(&denominator, &indices[i]) 53 | 54 | // (x - xj)/(xi - xj) 55 | denominator.Inverse(&denominator) 56 | numerator.ScalarMul(numerator, denominator) 57 | 58 | basis[i].Mul(basis[i], numerator) 59 | } 60 | } 61 | 62 | return Interpolator{basis} 63 | } 64 | 65 | // Interpolate takes a set of values representing polynomial evaluations, and 66 | // computes a polynomial that interpolates these values, storing the result in 67 | // `poly`. It is assumed that the values are in corresponding order to the 68 | // indices that were used to constructd the interpolator. That is, if the 69 | // interpolator was constructed using the set of indices `{x0, x1, ..., xn}`, 70 | // then calling this function with the values `{y0, y1, ..., yn}` will find the 71 | // interpolating polynomial for the set of points `{(x0, y0), (x1, y1), ..., 72 | // (xn, yn)}`. 73 | func (interp *Interpolator) Interpolate(values []secp256k1.Fn, poly *Poly) { 74 | // Polynomial is a linear combination of the Lagrange basis 75 | 76 | // In the first iteration we set the polynomial in case it was non-zero 77 | poly.Set(interp.basis[0]) 78 | poly.ScalarMul(*poly, values[0]) 79 | 80 | for i := 1; i < len(interp.basis); i++ { 81 | poly.AddScaled(*poly, interp.basis[i], values[i]) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /poly/interpolator_test.go: -------------------------------------------------------------------------------- 1 | package poly_test 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/renproject/secp256k1" 7 | 8 | . "github.com/onsi/ginkgo" 9 | . "github.com/onsi/gomega" 10 | . "github.com/renproject/shamir/poly" 11 | "github.com/renproject/shamir/poly/polyutil" 12 | "github.com/renproject/shamir/shamirutil" 13 | ) 14 | 15 | var _ = Describe("Polynomial interpolation", func() { 16 | Context("when interpolating polynomials", func() { 17 | It("should compute the correct interpolating polynomial", func() { 18 | trials := 100 19 | var degree int 20 | const maxPoints int = 15 21 | var numPoints int 22 | 23 | poly := NewWithCapacity(maxPoints + 1) 24 | interpPoly := NewWithCapacity(maxPoints + 1) 25 | values := make([]secp256k1.Fn, maxPoints) 26 | 27 | for i := 0; i < trials; i++ { 28 | numPoints = rand.Intn(maxPoints) + 1 29 | degree = rand.Intn(numPoints) 30 | 31 | indices := shamirutil.RandomIndices(numPoints) 32 | interpolator := NewInterpolator(indices) 33 | 34 | // Generate random polynomial and associated values 35 | values = values[:numPoints] 36 | polyutil.SetRandomPolynomial(&poly, degree) 37 | 38 | for j, index := range indices { 39 | values[j] = poly.Evaluate(index) 40 | } 41 | 42 | interpolator.Interpolate(values, &interpPoly) 43 | Expect(interpPoly.Eq(poly)).To(BeTrue()) 44 | } 45 | }) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /poly/marshal.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/secp256k1" 8 | "github.com/renproject/surge" 9 | ) 10 | 11 | // Generate implements the quick.Generator interface. 12 | func (p Poly) Generate(_ *rand.Rand, size int) reflect.Value { 13 | poly := make(Poly, size) 14 | for i := range poly { 15 | poly[i] = secp256k1.RandomFn() 16 | } 17 | return reflect.ValueOf(poly) 18 | } 19 | 20 | // SizeHint implements the surge.SizeHinter interface. 21 | func (p Poly) SizeHint() int { 22 | return 2*surge.SizeHintU32 + secp256k1.FnSizeMarshalled*len(p) 23 | } 24 | 25 | // Marshal implements the surge.Marshaler interface. 26 | func (p Poly) Marshal(buf []byte, rem int) ([]byte, int, error) { 27 | buf, rem, err := surge.MarshalLen(uint32(len(p)), buf, rem) 28 | if err != nil { 29 | return buf, rem, err 30 | } 31 | buf, rem, err = surge.MarshalLen(uint32(cap(p)), buf, rem) 32 | if err != nil { 33 | return buf, rem, err 34 | } 35 | for _, c := range p { 36 | buf, rem, err = c.Marshal(buf, rem) 37 | if err != nil { 38 | return buf, rem, err 39 | } 40 | } 41 | return buf, rem, nil 42 | } 43 | 44 | // Unmarshal implements the surge.Unmarshaler interface. 45 | func (p *Poly) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 46 | var l, c uint32 47 | buf, rem, err := surge.UnmarshalLen(&l, secp256k1.FnSize, buf, rem) 48 | if err != nil { 49 | return buf, rem, err 50 | } 51 | buf, rem, err = surge.UnmarshalLen(&c, secp256k1.FnSize, buf, rem) 52 | if err != nil { 53 | return buf, rem, err 54 | } 55 | if l == 0 { 56 | *p = make([]secp256k1.Fn, 0, c) 57 | return buf, rem, nil 58 | } 59 | if len(*p) < int(l) || cap(*p) < int(c) { 60 | *p = make(Poly, l, c) 61 | } else { 62 | *p = (*p)[:l] 63 | } 64 | for i := range *p { 65 | buf, rem, err = (*p)[i].Unmarshal(buf, rem) 66 | if err != nil { 67 | return buf, rem, err 68 | } 69 | } 70 | return buf, rem, nil 71 | } 72 | 73 | // Generate implements the quick.Generator interface. 74 | func (interp Interpolator) Generate(_ *rand.Rand, size int) reflect.Value { 75 | n := rand.Intn(size + 1) 76 | m := size / (n + 1) 77 | basis := make([]Poly, n) 78 | for i := range basis { 79 | basis[i] = make(Poly, m) 80 | for j := range basis[i] { 81 | basis[i][j] = secp256k1.RandomFn() 82 | } 83 | } 84 | return reflect.ValueOf(Interpolator{basis: basis}) 85 | } 86 | 87 | // SizeHint implements the surge.SizeHinter interface. 88 | func (interp Interpolator) SizeHint() int { return surge.SizeHint(interp.basis) } 89 | 90 | // Marshal implements the surge.Marshaler interface. 91 | func (interp Interpolator) Marshal(buf []byte, rem int) ([]byte, int, error) { 92 | return surge.Marshal(interp.basis, buf, rem) 93 | } 94 | 95 | // Unmarshal implements the surge.Unmarshaler interface. 96 | func (interp *Interpolator) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 97 | return surge.Unmarshal(&interp.basis, buf, rem) 98 | } 99 | -------------------------------------------------------------------------------- /poly/marshal_test.go: -------------------------------------------------------------------------------- 1 | package poly_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/renproject/surge/surgeutil" 8 | 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | . "github.com/renproject/shamir/poly" 12 | ) 13 | 14 | var _ = Describe("Surge marshalling", func() { 15 | trials := 100 16 | types := []reflect.Type{ 17 | reflect.TypeOf(Poly{}), 18 | reflect.TypeOf(Interpolator{}), 19 | } 20 | 21 | for _, t := range types { 22 | t := t 23 | 24 | Context(fmt.Sprintf("surge marshalling and unmarshalling for %v", t), func() { 25 | It("should be the same after marshalling and unmarshalling", func() { 26 | for i := 0; i < trials; i++ { 27 | Expect(surgeutil.MarshalUnmarshalCheck(t)).To(Succeed()) 28 | } 29 | }) 30 | 31 | It("should not panic when fuzzing", func() { 32 | for i := 0; i < trials; i++ { 33 | Expect(func() { surgeutil.Fuzz(t) }).ToNot(Panic()) 34 | } 35 | }) 36 | 37 | Context("marshalling", func() { 38 | It("should return an error when the buffer is too small", func() { 39 | for i := 0; i < trials; i++ { 40 | Expect(surgeutil.MarshalBufTooSmall(t)).To(Succeed()) 41 | } 42 | }) 43 | 44 | It("should return an error when the memory quota is too small", func() { 45 | for i := 0; i < trials; i++ { 46 | Expect(surgeutil.MarshalRemTooSmall(t)).To(Succeed()) 47 | } 48 | }) 49 | }) 50 | 51 | Context("unmarshalling", func() { 52 | It("should return an error when the buffer is too small", func() { 53 | for i := 0; i < trials; i++ { 54 | Expect(surgeutil.UnmarshalBufTooSmall(t)).To(Succeed()) 55 | } 56 | }) 57 | 58 | It("should return an error when the memory quota is too small", func() { 59 | for i := 0; i < trials; i++ { 60 | Expect(surgeutil.UnmarshalRemTooSmall(t)).To(Succeed()) 61 | } 62 | }) 63 | }) 64 | }) 65 | } 66 | }) 67 | -------------------------------------------------------------------------------- /poly/poly.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/renproject/secp256k1" 7 | "github.com/renproject/shamir/shamirutil" 8 | ) 9 | 10 | // Poly represents a polynomial in the field defined by the elliptic curve 11 | // secp256k1. That is, the field of integers modulo n where n is the order of 12 | // the secp256k1 group. 13 | // 14 | // A Poly can be indexed into, where index `i` will be the `i`th coefficient. 15 | // For example, the constant term is index 0. 16 | // 17 | // Since this type just aliases a slice, all of the considerations of using a 18 | // slice apply. For example, attempting to access coefficients outside of the 19 | // length bound will panic, as will using a polynomial in a context where its 20 | // capacity is too small (e.g. copying a polynomial that is too large for the 21 | // destination's capacity). The fact that this type is an alias also means that 22 | // it is possible to directly modify properties of the slice and the underlying 23 | // memory. To ensure the correct functioning of this type, these manual 24 | // modificaitons should be avoided, and rather the provided methods used. 25 | type Poly []secp256k1.Fn 26 | 27 | // NewFromSlice constructs a polynomial from a given slice. There will be no 28 | // further initialisation; the coefficients of the polynomial will be 29 | // determined by whatever values are currently in the slice, and the degree of 30 | // the polynomial will be determined by the length of the slice. Specifically, 31 | // the degree will be one less than the length of the slice. The capacity of 32 | // the polynomial will be the capacity of the slice. 33 | func NewFromSlice(coeffs []secp256k1.Fn) Poly { 34 | coeffsCopy := make([]secp256k1.Fn, len(coeffs)) 35 | copy(coeffsCopy, coeffs) 36 | poly := Poly(coeffsCopy) 37 | poly.removeLeadingZeros() 38 | return poly 39 | } 40 | 41 | // NewWithCapacity constructs a new polynomial with the given capacity. The 42 | // polynomial will also be initialised to the zero polynomial. 43 | // 44 | // NOTE: This function will panic if the argument is less than 1. 45 | func NewWithCapacity(c int) Poly { 46 | coeffs := make([]secp256k1.Fn, c) 47 | poly := Poly(coeffs) 48 | 49 | // Make it the zero polynomial 50 | poly.Zero() 51 | 52 | return poly 53 | } 54 | 55 | // String implements the Stringer interface 56 | func (p Poly) String() string { 57 | var coeff secp256k1.Fn 58 | 59 | coeff = *p.Coefficient(0) 60 | str := fmt.Sprintf("%v", coeff.Int()) 61 | 62 | for i := 1; i <= p.Degree(); i++ { 63 | coeff = *p.Coefficient(i) 64 | 65 | if i == 1 { 66 | str += fmt.Sprintf(" + %v x", coeff.Int()) 67 | } else { 68 | str += fmt.Sprintf(" + %v x^%v", coeff.Int(), i) 69 | } 70 | } 71 | 72 | return str 73 | } 74 | 75 | // Degree returns the degree of the polynomial. This is the exponent of the 76 | // highest term with non-zero coefficient. For example, 3x^2 + 2x + 1 has 77 | // degree 2. 78 | func (p Poly) Degree() int { 79 | return len(p) - 1 80 | } 81 | 82 | // Coefficient returns a pointer to the `i`th coefficient of the polynomial. 83 | // 84 | // NOTE: If `i` is geater than the degree of the polynomial, this function will 85 | // panic. 86 | func (p Poly) Coefficient(i int) *secp256k1.Fn { 87 | return &p[i] 88 | } 89 | 90 | // Set copies a given polynomial into the destination polynomial. Since the 91 | // memory is copied, the argument will remain unchanged. 92 | func (p *Poly) Set(a Poly) { 93 | // copy will only copy min(len(dst), len(src)) elements, so we need to make 94 | // sure that the destination slice has the right length 95 | p.setLenByDegree(a.Degree()) 96 | 97 | copy(*p, a) 98 | } 99 | 100 | // IsZero returns true if the polynomial is the zero polynomial, and false 101 | // otherwise. The zero polynomial is defined to have degree 0 and a constant 102 | // term that is equal to 0 (the additive identity in the field). 103 | func (p *Poly) IsZero() bool { 104 | truncated := *p 105 | truncated.removeLeadingZeros() 106 | if truncated.Degree() != 0 { 107 | return false 108 | } 109 | 110 | return truncated.Coefficient(0).IsZero() 111 | } 112 | 113 | // Eq returns true if the two polynomials are equal and false if they are not. 114 | // Equality of polynomials is defined as all coefficients being equal. 115 | func (p *Poly) Eq(other Poly) bool { 116 | truncated := *p 117 | truncated.removeLeadingZeros() 118 | other.removeLeadingZeros() 119 | 120 | // Short circuit if the polynomials have different degrees 121 | if truncated.Degree() != other.Degree() { 122 | return false 123 | } 124 | 125 | // Otherwise check each coefficient 126 | for i := range truncated { 127 | if !truncated.Coefficient(i).Eq(other.Coefficient(i)) { 128 | return false 129 | } 130 | } 131 | 132 | return true 133 | } 134 | 135 | // Zero sets the polynomial to the zero polynomial (additive identity). That 136 | // is, the polynomial of degree 0 with constant term coefficient also equal to 137 | // 0. 138 | func (p *Poly) Zero() { 139 | p.setLenByDegree(0) 140 | p.Coefficient(0).Clear() 141 | } 142 | 143 | // Sets the length of the underlying slice to be such that it can hold a 144 | // polynomial of the given degree. 145 | func (p *Poly) setLenByDegree(degree int) { 146 | (*p) = (*p)[:degree+1] 147 | } 148 | 149 | // Ensures that the x^deg(p) coefficient of the polynomial is non zero by 150 | // possibly reducing its Degree(). 151 | func (p *Poly) removeLeadingZeros() { 152 | for p.Degree() > 0 && p.Coefficient(p.Degree()).IsZero() { 153 | p.setLenByDegree(p.Degree() - 1) 154 | } 155 | } 156 | 157 | // Evaluate computes the value of the polynomial at the given point. 158 | func (p *Poly) Evaluate(x secp256k1.Fn) secp256k1.Fn { 159 | var res secp256k1.Fn 160 | 161 | res = *p.Coefficient(p.Degree()) 162 | 163 | for i := p.Degree() - 1; i >= 0; i-- { 164 | res.Mul(&res, &x) 165 | res.Add(&res, p.Coefficient(i)) 166 | } 167 | 168 | return res 169 | } 170 | 171 | // ScalarMul computes the multiplication of the input polynomial by the input 172 | // scale factor from the field that the polynomial is defined over. This 173 | // function is safe for aliasing: the argument may be an alias of the caller. 174 | // 175 | // NOTE: If the destination polynomial doesn't have sufficient capacity to 176 | // store the result, this function will panic. To ensure that the destination 177 | // has enough capacity, it is enough to ensure that the capacity is at least as 178 | // big as `deg(a) + 1`. 179 | func (p *Poly) ScalarMul(a Poly, s secp256k1.Fn) { 180 | // Short circuit conditions 181 | if s.IsZero() { 182 | p.Zero() 183 | return 184 | } 185 | if s.IsOne() { 186 | p.Set(a) 187 | return 188 | } 189 | 190 | p.setLenByDegree(a.Degree()) 191 | for i := range *p { 192 | p.Coefficient(i).Mul(a.Coefficient(i), &s) 193 | } 194 | } 195 | 196 | // Add computes the addition of the two input polynomials and stores the result 197 | // in the caller. This function is safe for aliasing: either (and possible 198 | // both) of the input polynomials may be an alias of the caller. 199 | // 200 | // NOTE: If the destination polynomial doesn't have sufficient capacity to 201 | // store the result, this function will panic. To ensure that the destination 202 | // has enough capacity, it is enough to ensure that the capacity is at least as 203 | // big as `max(deg(a), deg(b)) + 1`. It is possible that the result will have 204 | // degree smaller than this, but this will only happen in the case that some of 205 | // the leading terms cancel. 206 | func (p *Poly) Add(a, b Poly) { 207 | if a.Degree() > b.Degree() { 208 | p.setLenByDegree(a.Degree()) 209 | for i := range b { 210 | p.Coefficient(i).Add(a.Coefficient(i), b.Coefficient(i)) 211 | } 212 | copy((*p)[b.Degree()+1:], a[b.Degree()+1:]) 213 | } else { 214 | p.setLenByDegree(b.Degree()) 215 | for i := range a { 216 | p.Coefficient(i).Add(a.Coefficient(i), b.Coefficient(i)) 217 | } 218 | copy((*p)[a.Degree()+1:], b[a.Degree()+1:]) 219 | } 220 | 221 | if a.Degree() == b.Degree() { 222 | // Account for the fact that the leading coefficients of a and b may 223 | // have cancelled eachother 224 | p.removeLeadingZeros() 225 | } 226 | } 227 | 228 | // AddScaled computes the addition of the first polynomial and a scaled version 229 | // of the second polynomial and stores the result in the caller. This is 230 | // equivalent to doing the scaling and then the addition separately, but allows 231 | // for better memory efficiency. This function is safe for aliasing: either 232 | // (and possible both) of the input polynomials may be an alias of the caller. 233 | // 234 | // NOTE: If the destination polynomial doesn't have sufficient capacity to 235 | // store the result, this function will panic. To ensure that the destination 236 | // has enough capacity, it is enough to ensure that the capacity is at least as 237 | // big as `max(deg(a), deg(b)) + 1`. It is possible that the result will have 238 | // degree smaller than this, but this will only happen in the case that some of 239 | // the leading terms cancel. 240 | func (p *Poly) AddScaled(a, b Poly, s secp256k1.Fn) { 241 | var scaled secp256k1.Fn 242 | 243 | if a.Degree() > b.Degree() { 244 | p.setLenByDegree(a.Degree()) 245 | for i := range b { 246 | scaled.Mul(&s, b.Coefficient(i)) 247 | p.Coefficient(i).Add(a.Coefficient(i), &scaled) 248 | } 249 | copy((*p)[b.Degree()+1:], a[b.Degree()+1:]) 250 | } else { 251 | p.setLenByDegree(b.Degree()) 252 | for i := range a { 253 | scaled.Mul(&s, b.Coefficient(i)) 254 | p.Coefficient(i).Add(a.Coefficient(i), &scaled) 255 | } 256 | for i := a.Degree() + 1; i <= b.Degree(); i++ { 257 | p.Coefficient(i).Mul(b.Coefficient(i), &s) 258 | } 259 | } 260 | 261 | if a.Degree() == b.Degree() { 262 | // Account for the fact that the leading coefficients of a and b may 263 | // have cancelled eachother 264 | p.removeLeadingZeros() 265 | } 266 | } 267 | 268 | // Sub subtracts the second polynomial from the first polynomial and stores the 269 | // result in the destination polynomial. This function is safe for aliasing: 270 | // either (and possible both) of the input polynomials may be an alias of the 271 | // caller. 272 | // 273 | // NOTE: If the destination polynomial doesn't have sufficient capacity to 274 | // store the result, this function will panic. To ensure that the destination 275 | // has enough capacity, it is enough to ensure that the capacity is at least as 276 | // big as `max(deg(a), deg(b)) + 1`. It is possible that the result will have 277 | // degree smaller than this, but this will only happen in the case that some of 278 | // the leading terms cancel. 279 | func (p *Poly) Sub(a, b Poly) { 280 | // Temporary value to store the negative of the coefficients from b 281 | var neg secp256k1.Fn 282 | 283 | if a.Degree() > b.Degree() { 284 | p.setLenByDegree(a.Degree()) 285 | for i := range b { 286 | neg.Negate(b.Coefficient(i)) 287 | p.Coefficient(i).Add(a.Coefficient(i), &neg) 288 | } 289 | 290 | // The remaining coefficients are just those of a 291 | copy((*p)[b.Degree()+1:], a[b.Degree()+1:]) 292 | } else { 293 | p.setLenByDegree(b.Degree()) 294 | for i := range a { 295 | neg.Negate(b.Coefficient(i)) 296 | p.Coefficient(i).Add(a.Coefficient(i), &neg) 297 | } 298 | 299 | // The remaining terms are negatives of the coefficients of b 300 | for i := a.Degree() + 1; i <= b.Degree(); i++ { 301 | p.Coefficient(i).Negate(b.Coefficient(i)) 302 | } 303 | } 304 | 305 | if a.Degree() == b.Degree() { 306 | // Account for the fact that the leading coefficients of a and b may 307 | // have cancelled eachother 308 | p.removeLeadingZeros() 309 | } 310 | } 311 | 312 | // Neg computes the negation of the polynomial and stores it in the destination 313 | // polynomial. This function is safe for aliasing: the argument may be an alias 314 | // of the caller. 315 | // 316 | // NOTE: If the destination polynomial doesn't have sufficient capacity to 317 | // store the result, this function will panic. To ensure that the destination 318 | // has enough capacity, it is enough to ensure that the capacity is at least as 319 | // big as `deg(a) + 1`. 320 | func (p *Poly) Neg(a Poly) { 321 | // Zero out any leading terms of higher Degree() than a 322 | p.setLenByDegree(a.Degree()) 323 | 324 | for i := range *p { 325 | p.Coefficient(i).Negate(a.Coefficient(i)) 326 | } 327 | } 328 | 329 | // Mul copmutes the product of the two polynomials and stores the result in the 330 | // destination polynomial. This function is not safe when the two input 331 | // polynomials are aliases of eachother as well as the destination; in this 332 | // case the multiplication will give an incorrect result. The exception to this 333 | // is when the polynomial has degree 0. Otherwise, either input polynomial may 334 | // individually be an alias of the destination polynomial or be aliases of 335 | // eachother (but not the destination) and still be safe. 336 | // 337 | // NOTE: If the destination polynomial doesn't have sufficient capacity to 338 | // store the result, this function will panic. To ensure that the destination 339 | // has enough capacity, it is enough to ensure that the capacity is at least as 340 | // big as `deg(a) + deg(b) + 1`. 341 | func (p *Poly) Mul(a, b Poly) { 342 | // Short circuit if either polynomial is zero 343 | if a.IsZero() || b.IsZero() { 344 | p.Zero() 345 | return 346 | } 347 | 348 | // In order to allow for the case that p == a or p == b, we need to make 349 | // sure that we do not clobber coefficients before we have finished using 350 | // them. To do this, we populate the higher Degree() coefficients first. 351 | // However, we need to consider that, for instance, the coefficient for the 352 | // x^1 term is equal to a0b1 + a1b0, which clearly uses the x^1 coefficient 353 | // of both a and b. We therefore need to check which of a and b are aliased 354 | // by p, and make sure to use the higher Degree() coefficient of the 355 | // aliased polynomial first in our sum, before it gets clobbered. 356 | // 357 | // We need to check that the slices point to the same memory. Go doesn't 358 | // allow comparison of slices other than to the nil value, so we use the 359 | // following workaround wherein we compare the addresses of the first 360 | // elements of the slices instead. 361 | aliasedA := p.Coefficient(0) == a.Coefficient(0) 362 | 363 | p.setLenByDegree(a.Degree() + b.Degree()) 364 | var aStart, bStart, numTerms int 365 | var ab secp256k1.Fn 366 | 367 | // If p aliases a, then we need to count down in the coefficients of a to 368 | // avoid clobbering values that we will need to use 369 | if aliasedA { 370 | for i := a.Degree() + b.Degree(); i >= 0; i-- { 371 | aStart = shamirutil.Min(a.Degree(), i) 372 | bStart = shamirutil.Max(0, i-a.Degree()) 373 | numTerms = shamirutil.Min(aStart, b.Degree()-bStart) 374 | 375 | // Account for the fact that initially the memory might not be 376 | // zeroed 377 | ab.Mul(a.Coefficient(aStart), b.Coefficient(bStart)) 378 | *p.Coefficient(i) = ab 379 | 380 | for j := 1; j <= numTerms; j++ { 381 | // Count down in a and up in b 382 | ab.Mul(a.Coefficient(aStart-j), b.Coefficient(bStart+j)) 383 | p.Coefficient(i).Add(p.Coefficient(i), &ab) 384 | } 385 | } 386 | } else { 387 | // It is possible that p does not aliase either a or b here, but in 388 | // this case either of the branches would work so we don't need to 389 | // consider this case separately 390 | 391 | for i := a.Degree() + b.Degree(); i >= 0; i-- { 392 | aStart = shamirutil.Max(0, i-b.Degree()) 393 | bStart = shamirutil.Min(b.Degree(), i) 394 | numTerms = shamirutil.Min(a.Degree()-aStart, bStart) 395 | 396 | // Account for the fact that initially the memory might not be 397 | // zeroed 398 | ab.Mul(a.Coefficient(aStart), b.Coefficient(bStart)) 399 | *p.Coefficient(i) = ab 400 | 401 | for j := 1; j <= numTerms; j++ { 402 | // Count up in a and down in b 403 | ab.Mul(a.Coefficient(aStart+j), b.Coefficient(bStart-j)) 404 | p.Coefficient(i).Add(p.Coefficient(i), &ab) 405 | } 406 | } 407 | } 408 | } 409 | 410 | // Divide computes the division of `a` by `b`, storing the quotient in `q` and 411 | // the remainder in `r`. That is, after calling this function, the polynomials 412 | // should satisfy `a = bq + r`. Note that if either `q` or `r` are aliased by 413 | // either `a` or `b`, the result will be incorrect. This is also true if `q` is 414 | // an alias of `r`. The inputs `a` and `b` are not modified and can therefore 415 | // also be aliases of eachother. 416 | // 417 | // NOTE: If the destination polynomials (i.e. `q` and `r`) don't have 418 | // sufficient capacity to store the result, this function will panic. To ensure 419 | // that these polynomials have enough capacity, it is sufficient to ensure that 420 | // `q` has a capacity of at least `deg(a) - deg(b) + 1`, and that `r` has a 421 | // capacity of at least `deg(a) + 1`. 422 | func Divide(a, b Poly, q, r *Poly) { 423 | // Short circuit when the division is trivial 424 | if b.Degree() > a.Degree() { 425 | q.Zero() 426 | r.Set(a) 427 | return 428 | } 429 | 430 | var c, s, bs, cInv secp256k1.Fn 431 | var d, diff int 432 | 433 | r.Set(a) 434 | d = b.Degree() 435 | c = *b.Coefficient(b.Degree()) 436 | q.setLenByDegree(r.Degree() - d) 437 | cInv.Inverse(&c) 438 | 439 | for r.Degree() >= d { 440 | s.Mul(&cInv, r.Coefficient(r.Degree())) 441 | 442 | // q = q + sx^(deg(r) - d) 443 | diff = r.Degree() - d 444 | *q.Coefficient(diff) = s 445 | 446 | // r = r - b sx^(deg(r) - d) 447 | for i := range b { 448 | bs.Mul(&s, b.Coefficient(i)) 449 | bs.Negate(&bs) 450 | r.Coefficient(diff+i).Add(r.Coefficient(diff+i), &bs) 451 | } 452 | r.setLenByDegree(r.Degree() - 1) 453 | r.removeLeadingZeros() 454 | } 455 | 456 | // In the case that r = 0, we need to fix the data representation 457 | if len(*r) == 0 { 458 | r.Zero() 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /poly/poly_suite_test.go: -------------------------------------------------------------------------------- 1 | package poly_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestPoly(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Poly Suite") 13 | } 14 | -------------------------------------------------------------------------------- /poly/poly_test.go: -------------------------------------------------------------------------------- 1 | package poly_test 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/renproject/secp256k1" 8 | 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | . "github.com/renproject/shamir/poly" 12 | "github.com/renproject/shamir/poly/polyutil" 13 | "github.com/renproject/shamir/shamirutil" 14 | ) 15 | 16 | var _ = Describe("Polynomials", func() { 17 | var zero, one secp256k1.Fn 18 | 19 | zero.SetU16(0) 20 | one.SetU16(1) 21 | 22 | It("should implement the Stringer interface", func() { 23 | poly := NewWithCapacity(10) 24 | polyutil.SetRandomPolynomial(&poly, 9) 25 | _ = poly.String() 26 | }) 27 | 28 | Context("when constructing a polynomial from a slice", func() { 29 | Specify("the coefficients should correspond to the slice elements", func() { 30 | trials := 1000 31 | maxDegree := 20 32 | 33 | var coefficients [21]secp256k1.Fn 34 | var degree int 35 | 36 | for i := 0; i < trials; i++ { 37 | degree = rand.Intn(maxDegree + 1) 38 | for i := 0; i <= degree; i++ { 39 | coefficients[i] = secp256k1.RandomFn() 40 | } 41 | poly := NewFromSlice(coefficients[:degree+1]) 42 | 43 | for i := range poly { 44 | Expect(poly.Coefficient(i).Eq(&coefficients[i])).To(BeTrue()) 45 | } 46 | } 47 | }) 48 | }) 49 | 50 | Context("when constructing a polynomial with a given capacity", func() { 51 | Specify("it should be zeroed and have the given capacity", func() { 52 | trials := 1000 53 | maxDegree := 20 54 | 55 | var c int 56 | 57 | for i := 0; i < trials; i++ { 58 | c = rand.Intn(maxDegree) + 1 59 | poly := NewWithCapacity(c) 60 | 61 | // It should be initialised to the zero polynomial 62 | Expect(poly.IsZero()).To(BeTrue()) 63 | 64 | // It should have the right capacity 65 | Expect(cap(poly)).To(Equal(c)) 66 | } 67 | }) 68 | }) 69 | 70 | Context("when getting the degree of a polynomial", func() { 71 | It("should be correct", func() { 72 | trials := 1000 73 | maxDegree := 20 74 | 75 | var degree int 76 | 77 | poly := NewWithCapacity(maxDegree + 1) 78 | 79 | for i := 0; i < trials; i++ { 80 | degree = rand.Intn(maxDegree + 1) 81 | polyutil.SetRandomPolynomial(&poly, degree) 82 | 83 | Expect(poly.Degree()).To(Equal(degree)) 84 | 85 | // Memory locations beyond the degree should be out of bounds 86 | Expect(func() { _ = poly.Coefficient(degree + 1) }).To(Panic()) 87 | } 88 | }) 89 | }) 90 | 91 | Context("when setting a polynomial to be equal to another", func() { 92 | It("should correctly copy into the destination", func() { 93 | trials := 1000 94 | maxDegree := 20 95 | 96 | var degreeA, degreeB int 97 | 98 | a := NewWithCapacity(maxDegree + 1) 99 | b := NewWithCapacity(maxDegree + 1) 100 | 101 | for i := 0; i < trials; i++ { 102 | degreeA = rand.Intn(maxDegree + 1) 103 | degreeB = rand.Intn(maxDegree + 1) 104 | polyutil.SetRandomPolynomial(&a, degreeA) 105 | polyutil.SetRandomPolynomial(&b, degreeB) 106 | 107 | a.Set(b) 108 | 109 | Expect(a.Eq(b)).To(BeTrue()) 110 | } 111 | }) 112 | }) 113 | 114 | Context("when checking if a polynomial is zero", func() { 115 | It("should return true when given the zero polynomial", func() { 116 | poly := NewWithCapacity(1) 117 | poly = poly[:1] 118 | poly[0] = zero 119 | 120 | Expect(poly.IsZero()).To(BeTrue()) 121 | 122 | // Additional leading zeros should not affect the result. 123 | for i := 2; i < 10; i++ { 124 | poly = append(poly, secp256k1.Fn{}) 125 | Expect(poly.IsZero()).To(BeTrue()) 126 | Expect(len(poly)).To(Equal(i)) 127 | } 128 | }) 129 | 130 | It("should return false when given a polynomial with degree 0 but non-zero constant term", func() { 131 | poly := NewWithCapacity(1) 132 | poly = poly[:1] 133 | poly[0] = secp256k1.RandomFn() 134 | 135 | // Ensure that the constant term is non-zero 136 | for poly.Coefficient(0).IsZero() { 137 | poly[0] = secp256k1.RandomFn() 138 | } 139 | 140 | Expect(poly.IsZero()).To(BeFalse()) 141 | }) 142 | 143 | It("should return false when given a polynomial with degree greater than zero", func() { 144 | trials := 1000 145 | maxDegree := 20 146 | 147 | var degree int 148 | 149 | poly := NewWithCapacity(maxDegree + 1) 150 | 151 | for i := 0; i < trials; i++ { 152 | degree = rand.Intn(maxDegree) + 1 153 | polyutil.SetRandomPolynomial(&poly, degree) 154 | 155 | Expect(poly.IsZero()).To(BeFalse()) 156 | 157 | // Should also return false when the constant term is zero 158 | poly.Coefficient(0).Clear() 159 | Expect(poly.IsZero()).To(BeFalse()) 160 | } 161 | }) 162 | }) 163 | 164 | Context("when checking if two polynomials are equal", func() { 165 | It("should return true if the polynomials are equal", func() { 166 | trials := 1000 167 | maxDegree := 20 168 | 169 | var degree int 170 | 171 | a := NewWithCapacity(maxDegree + 1) 172 | b := NewWithCapacity(maxDegree + 1) 173 | 174 | for i := 0; i < trials; i++ { 175 | degree = rand.Intn(maxDegree + 1) 176 | polyutil.SetRandomPolynomial(&a, degree) 177 | b.Set(a) 178 | 179 | Expect(a.Eq(b)).To(BeTrue()) 180 | 181 | // The result should remain true when there are additional 182 | // leading zeros. 183 | aEx := append(a, secp256k1.Fn{}) 184 | bEx := append(b, secp256k1.Fn{}) 185 | 186 | Expect(aEx.Eq(b)).To(BeTrue()) 187 | Expect(len(aEx)).To(Equal(len(a) + 1)) 188 | 189 | Expect(a.Eq(bEx)).To(BeTrue()) 190 | Expect(len(bEx)).To(Equal(len(b) + 1)) 191 | 192 | Expect(aEx.Eq(bEx)).To(BeTrue()) 193 | Expect(len(aEx)).To(Equal(len(a) + 1)) 194 | Expect(len(bEx)).To(Equal(len(b) + 1)) 195 | } 196 | }) 197 | 198 | It("should return false if the polynomials are not equal", func() { 199 | trials := 1000 200 | maxDegree := 20 201 | 202 | var degree int 203 | 204 | a := NewWithCapacity(maxDegree + 1) 205 | b := NewWithCapacity(maxDegree + 1) 206 | 207 | for i := 0; i < trials; i++ { 208 | degree = rand.Intn(maxDegree + 1) 209 | polyutil.SetRandomPolynomial(&a, degree) 210 | 211 | // Generate a non-zero polynomial 212 | polyutil.SetRandomPolynomial(&b, degree) 213 | for b.IsZero() { 214 | polyutil.SetRandomPolynomial(&b, degree) 215 | } 216 | 217 | // Gauranteed to be different from a since we are adding a 218 | // non-zero polynomial to a 219 | b.Add(a, b) 220 | 221 | Expect(a.Eq(b)).To(BeFalse()) 222 | } 223 | }) 224 | 225 | It("should return false if the polynomials have different degrees", func() { 226 | trials := 1000 227 | maxDegree := 20 228 | 229 | var degreeA, degreeB int 230 | 231 | a := NewWithCapacity(maxDegree + 1) 232 | b := NewWithCapacity(maxDegree + 1) 233 | 234 | for i := 0; i < trials; i++ { 235 | degreeA = rand.Intn(maxDegree + 1) 236 | degreeB = rand.Intn(maxDegree + 1) 237 | for degreeB == degreeA { 238 | degreeB = rand.Intn(maxDegree + 1) 239 | } 240 | polyutil.SetRandomPolynomial(&a, degreeA) 241 | polyutil.SetRandomPolynomial(&b, degreeB) 242 | 243 | Expect(a.Eq(b)).To(BeFalse()) 244 | } 245 | }) 246 | }) 247 | 248 | Context("when zeroing a polynomial", func() { 249 | It("should set the polynomial to the zero polynomial", func() { 250 | trials := 1000 251 | maxDegree := 20 252 | 253 | var degree int 254 | 255 | poly := NewWithCapacity(maxDegree + 1) 256 | 257 | for i := 0; i < trials; i++ { 258 | degree = rand.Intn(maxDegree + 1) 259 | polyutil.SetRandomPolynomial(&poly, degree) 260 | 261 | poly.Zero() 262 | 263 | Expect(poly.IsZero()).To(BeTrue()) 264 | } 265 | }) 266 | }) 267 | 268 | Context("when evaluating a polynomial at a point", func() { 269 | It("should perform the computation correctly", func() { 270 | trials := 1000 271 | maxDegree := 20 272 | 273 | var x, y, eval, term secp256k1.Fn 274 | var degree int 275 | 276 | poly := NewWithCapacity(maxDegree + 1) 277 | 278 | for i := 0; i < trials; i++ { 279 | degree = rand.Intn(maxDegree + 1) 280 | polyutil.SetRandomPolynomial(&poly, degree) 281 | x = secp256k1.RandomFn() 282 | 283 | // Manually evaluate the polynomial 284 | y = zero 285 | for j := 0; j <= poly.Degree(); j++ { 286 | term = *poly.Coefficient(j) 287 | for k := 0; k < j; k++ { 288 | term.Mul(&term, &x) 289 | } 290 | 291 | y.Add(&y, &term) 292 | } 293 | 294 | eval = poly.Evaluate(x) 295 | Expect(eval.Eq(&y)).To(BeTrue()) 296 | } 297 | }) 298 | }) 299 | 300 | Context("when scaling a polynomial", func() { 301 | Specify("the defining relation should hold", func() { 302 | trials := 1000 303 | maxDegree := 20 304 | 305 | var scale, term secp256k1.Fn 306 | var degree int 307 | 308 | a := NewWithCapacity(maxDegree + 1) 309 | b := NewWithCapacity(maxDegree + 1) 310 | 311 | for i := 0; i < trials; i++ { 312 | degree = rand.Intn(maxDegree + 1) 313 | polyutil.SetRandomPolynomial(&a, degree) 314 | scale = secp256k1.RandomFn() 315 | b.ScalarMul(a, scale) 316 | 317 | for j := 0; j <= b.Degree(); j++ { 318 | term.Mul(&scale, a.Coefficient(j)) 319 | Expect(b.Coefficient(j).Eq(&term)).To(BeTrue()) 320 | } 321 | } 322 | }) 323 | 324 | It("should yield zero when the scalar is zero", func() { 325 | trials := 1000 326 | maxDegree := 20 327 | 328 | var degree int 329 | 330 | a := NewWithCapacity(maxDegree + 1) 331 | b := NewWithCapacity(maxDegree + 1) 332 | 333 | for i := 0; i < trials; i++ { 334 | degree = rand.Intn(maxDegree + 1) 335 | polyutil.SetRandomPolynomial(&a, degree) 336 | b.ScalarMul(a, zero) 337 | 338 | Expect(b.IsZero()).To(BeTrue()) 339 | } 340 | }) 341 | 342 | It("should be the same when the scalar is one", func() { 343 | trials := 1000 344 | maxDegree := 20 345 | 346 | var degree int 347 | 348 | a := NewWithCapacity(maxDegree + 1) 349 | b := NewWithCapacity(maxDegree + 1) 350 | 351 | for i := 0; i < trials; i++ { 352 | degree = rand.Intn(maxDegree + 1) 353 | polyutil.SetRandomPolynomial(&a, degree) 354 | b.ScalarMul(a, one) 355 | 356 | Expect(b.Eq(a)).To(BeTrue()) 357 | } 358 | }) 359 | 360 | It("should work when the argument is an alias of the caller", func() { 361 | trials := 1000 362 | maxDegree := 20 363 | 364 | var scale secp256k1.Fn 365 | var degree int 366 | 367 | a := NewWithCapacity(maxDegree + 1) 368 | b := NewWithCapacity(maxDegree + 1) 369 | 370 | for i := 0; i < trials; i++ { 371 | degree = rand.Intn(maxDegree + 1) 372 | polyutil.SetRandomPolynomial(&a, degree) 373 | scale = secp256k1.RandomFn() 374 | b.ScalarMul(a, scale) 375 | a.ScalarMul(a, scale) 376 | 377 | Expect(a.Eq(b)).To(BeTrue()) 378 | } 379 | }) 380 | }) 381 | 382 | Context("when adding polynomials", func() { 383 | Specify("the defining relationship should hold", func() { 384 | trials := 1000 385 | maxDegree := 20 386 | 387 | var coeff secp256k1.Fn 388 | var degreeA, degreeB int 389 | 390 | a := NewWithCapacity(maxDegree + 1) 391 | b := NewWithCapacity(maxDegree + 1) 392 | c := NewWithCapacity(maxDegree + 1) 393 | 394 | for i := 0; i < trials; i++ { 395 | degreeA = rand.Intn(maxDegree + 1) 396 | degreeB = rand.Intn(maxDegree + 1) 397 | polyutil.SetRandomPolynomial(&a, degreeA) 398 | polyutil.SetRandomPolynomial(&b, degreeB) 399 | c.Add(a, b) 400 | 401 | // Check the coefficients 402 | for i := 0; i < shamirutil.Max(degreeA, degreeB); i++ { 403 | if i > degreeA { 404 | coeff.Add(&zero, &b[i]) 405 | } else if i > degreeB { 406 | coeff.Add(&a[i], &zero) 407 | } else { 408 | coeff.Add(&a[i], &b[i]) 409 | } 410 | Expect(c[i].Eq(&coeff)).To(BeTrue()) 411 | } 412 | 413 | // Check the degree 414 | Expect(addCheckDegree(a, b, c)).To(BeTrue()) 415 | } 416 | }) 417 | 418 | Specify("the degree should be correct if leading coefficients cancel", func() { 419 | trials := 1000 420 | maxDegree := 20 421 | 422 | var degree, leadingSame int 423 | 424 | a := NewWithCapacity(maxDegree + 1) 425 | b := NewWithCapacity(maxDegree + 1) 426 | c := NewWithCapacity(maxDegree + 1) 427 | 428 | for i := 0; i < trials; i++ { 429 | degree = rand.Intn(maxDegree + 1) 430 | leadingSame = rand.Intn(degree + 1) 431 | polyutil.SetRandomPolynomial(&a, degree) 432 | 433 | b = b[:len(a)] 434 | for i := range b { 435 | if i < degree+1-leadingSame { 436 | // lower coefficients are random but different to a 437 | b[i] = secp256k1.RandomFn() 438 | for b[i].Eq(&a[i]) { 439 | b[i] = secp256k1.RandomFn() 440 | } 441 | } else { 442 | // upper leadingSame coefficients are the negation of a 443 | b[i].Negate(&a[i]) 444 | } 445 | } 446 | 447 | c.Add(a, b) 448 | 449 | // Check the degree 450 | Expect(addCheckDegree(a, b, c)).To(BeTrue()) 451 | } 452 | }) 453 | 454 | It("should work when the first argument is a alias of the caller", func() { 455 | trials := 1000 456 | maxDegree := 20 457 | 458 | var degreeA, degreeB int 459 | 460 | a := NewWithCapacity(maxDegree + 1) 461 | b := NewWithCapacity(maxDegree + 1) 462 | c := NewWithCapacity(maxDegree + 1) 463 | 464 | for i := 0; i < trials; i++ { 465 | degreeA = rand.Intn(maxDegree + 1) 466 | degreeB = rand.Intn(maxDegree + 1) 467 | polyutil.SetRandomPolynomial(&a, degreeA) 468 | polyutil.SetRandomPolynomial(&b, degreeB) 469 | c.Add(a, b) 470 | a.Add(a, b) 471 | 472 | Expect(a.Eq(c)).To(BeTrue()) 473 | } 474 | }) 475 | 476 | It("should work when the second argument is an alias of the caller", func() { 477 | trials := 1000 478 | maxDegree := 20 479 | 480 | var degreeA, degreeB int 481 | 482 | a := NewWithCapacity(maxDegree + 1) 483 | b := NewWithCapacity(maxDegree + 1) 484 | c := NewWithCapacity(maxDegree + 1) 485 | 486 | for i := 0; i < trials; i++ { 487 | degreeA = rand.Intn(maxDegree + 1) 488 | degreeB = rand.Intn(maxDegree + 1) 489 | polyutil.SetRandomPolynomial(&a, degreeA) 490 | polyutil.SetRandomPolynomial(&b, degreeB) 491 | c.Add(a, b) 492 | b.Add(a, b) 493 | 494 | Expect(b.Eq(c)).To(BeTrue()) 495 | } 496 | }) 497 | 498 | It("should work when the first argument is an alias of the second", func() { 499 | trials := 1000 500 | maxDegree := 20 501 | 502 | var degree int 503 | 504 | a := NewWithCapacity(maxDegree + 1) 505 | b := NewWithCapacity(maxDegree + 1) 506 | c := NewWithCapacity(maxDegree + 1) 507 | d := NewWithCapacity(maxDegree + 1) 508 | 509 | for i := 0; i < trials; i++ { 510 | degree = rand.Intn(maxDegree + 1) 511 | polyutil.SetRandomPolynomial(&a, degree) 512 | b.Set(a) 513 | c.Add(a, b) 514 | d.Add(a, a) 515 | 516 | Expect(d.Eq(c)).To(BeTrue()) 517 | } 518 | }) 519 | 520 | It("should work when the caller and both arguments are aliases of each other", func() { 521 | trials := 1000 522 | maxDegree := 20 523 | 524 | var degree int 525 | 526 | a := NewWithCapacity(maxDegree + 1) 527 | b := NewWithCapacity(maxDegree + 1) 528 | c := NewWithCapacity(maxDegree + 1) 529 | 530 | for i := 0; i < trials; i++ { 531 | degree = rand.Intn(maxDegree + 1) 532 | polyutil.SetRandomPolynomial(&a, degree) 533 | b.Set(a) 534 | c.Add(a, b) 535 | a.Add(a, a) 536 | 537 | Expect(a.Eq(c)).To(BeTrue()) 538 | } 539 | }) 540 | }) 541 | 542 | Context("when adding scaled polynomials", func() { 543 | It("should behave the same as adding a scaled version of the second argument", func() { 544 | trials := 1000 545 | maxDegree := 20 546 | 547 | var scale secp256k1.Fn 548 | var degreeA, degreeB int 549 | 550 | a := NewWithCapacity(maxDegree + 1) 551 | b := NewWithCapacity(maxDegree + 1) 552 | c := NewWithCapacity(maxDegree + 1) 553 | scaleAdd := NewWithCapacity(maxDegree + 1) 554 | 555 | for i := 0; i < trials; i++ { 556 | scale = secp256k1.RandomFn() 557 | 558 | degreeA = rand.Intn(maxDegree + 1) 559 | degreeB = rand.Intn(maxDegree + 1) 560 | polyutil.SetRandomPolynomial(&a, degreeA) 561 | polyutil.SetRandomPolynomial(&b, degreeB) 562 | c.AddScaled(a, b, scale) 563 | 564 | scaleAdd.ScalarMul(b, scale) 565 | scaleAdd.Add(a, scaleAdd) 566 | 567 | Expect(c.Eq(scaleAdd)).To(BeTrue()) 568 | } 569 | }) 570 | 571 | It("should work when the first argument is a alias of the caller", func() { 572 | trials := 1000 573 | maxDegree := 20 574 | 575 | var scale secp256k1.Fn 576 | var degree int 577 | 578 | a := NewWithCapacity(maxDegree + 1) 579 | b := NewWithCapacity(maxDegree + 1) 580 | c := NewWithCapacity(maxDegree + 1) 581 | 582 | for i := 0; i < trials; i++ { 583 | scale = secp256k1.RandomFn() 584 | 585 | degree = rand.Intn(maxDegree + 1) 586 | polyutil.SetRandomPolynomial(&a, degree) 587 | polyutil.SetRandomPolynomial(&b, degree) 588 | c.AddScaled(a, b, scale) 589 | a.AddScaled(a, b, scale) 590 | 591 | Expect(a.Eq(c)).To(BeTrue()) 592 | } 593 | }) 594 | 595 | It("should work when the second argument is an alias of the caller", func() { 596 | trials := 1000 597 | maxDegree := 20 598 | 599 | var scale secp256k1.Fn 600 | var degree int 601 | 602 | a := NewWithCapacity(maxDegree + 1) 603 | b := NewWithCapacity(maxDegree + 1) 604 | c := NewWithCapacity(maxDegree + 1) 605 | 606 | for i := 0; i < trials; i++ { 607 | scale = secp256k1.RandomFn() 608 | 609 | degree = rand.Intn(maxDegree + 1) 610 | polyutil.SetRandomPolynomial(&a, degree) 611 | polyutil.SetRandomPolynomial(&b, degree) 612 | c.AddScaled(a, b, scale) 613 | b.AddScaled(a, b, scale) 614 | 615 | Expect(b.Eq(c)).To(BeTrue()) 616 | } 617 | }) 618 | 619 | It("should work when the first argument is an alias of the second", func() { 620 | trials := 1000 621 | maxDegree := 20 622 | 623 | var scale secp256k1.Fn 624 | var degree int 625 | 626 | a := NewWithCapacity(maxDegree + 1) 627 | b := NewWithCapacity(maxDegree + 1) 628 | c := NewWithCapacity(maxDegree + 1) 629 | d := NewWithCapacity(maxDegree + 1) 630 | 631 | for i := 0; i < trials; i++ { 632 | scale = secp256k1.RandomFn() 633 | 634 | degree = rand.Intn(maxDegree + 1) 635 | polyutil.SetRandomPolynomial(&a, degree) 636 | b.Set(a) 637 | c.AddScaled(a, b, scale) 638 | d.AddScaled(a, a, scale) 639 | 640 | Expect(d.Eq(c)).To(BeTrue()) 641 | } 642 | }) 643 | 644 | It("should work when the caller and both arguments are aliases of each other", func() { 645 | trials := 1000 646 | maxDegree := 20 647 | 648 | var scale secp256k1.Fn 649 | var degree int 650 | 651 | a := NewWithCapacity(maxDegree + 1) 652 | b := NewWithCapacity(maxDegree + 1) 653 | c := NewWithCapacity(maxDegree + 1) 654 | 655 | for i := 0; i < trials; i++ { 656 | scale = secp256k1.RandomFn() 657 | 658 | degree = rand.Intn(maxDegree + 1) 659 | polyutil.SetRandomPolynomial(&a, degree) 660 | b.Set(a) 661 | c.AddScaled(a, b, scale) 662 | a.AddScaled(a, a, scale) 663 | 664 | Expect(a.Eq(c)).To(BeTrue()) 665 | } 666 | }) 667 | }) 668 | 669 | Context("when subtracting polynomials", func() { 670 | Specify("the defining relationship should hold", func() { 671 | trials := 1000 672 | maxDegree := 20 673 | 674 | var coeff secp256k1.Fn 675 | var degreeA, degreeB int 676 | 677 | a := NewWithCapacity(maxDegree + 1) 678 | b := NewWithCapacity(maxDegree + 1) 679 | c := NewWithCapacity(maxDegree + 1) 680 | 681 | for i := 0; i < trials; i++ { 682 | degreeA = rand.Intn(maxDegree + 1) 683 | degreeB = rand.Intn(maxDegree + 1) 684 | polyutil.SetRandomPolynomial(&a, degreeA) 685 | polyutil.SetRandomPolynomial(&b, degreeB) 686 | c.Sub(a, b) 687 | 688 | // Check the coefficients 689 | for i := 0; i < shamirutil.Max(degreeA, degreeB); i++ { 690 | if i > degreeA { 691 | coeff.Negate(&b[i]) 692 | } else if i > degreeB { 693 | coeff = a[i] 694 | } else { 695 | coeff.Negate(&b[i]) 696 | coeff.Add(&a[i], &coeff) 697 | } 698 | Expect(c[i].Eq(&coeff)).To(BeTrue()) 699 | } 700 | 701 | // Check the degree 702 | Expect(subCheckDegree(a, b, c)).To(BeTrue()) 703 | } 704 | }) 705 | 706 | Specify("the degree should be correct if leading coefficients cancel", func() { 707 | trials := 1000 708 | maxDegree := 20 709 | 710 | var degree, leadingSame int 711 | 712 | a := NewWithCapacity(maxDegree + 1) 713 | b := NewWithCapacity(maxDegree + 1) 714 | c := NewWithCapacity(maxDegree + 1) 715 | 716 | for i := 0; i < trials; i++ { 717 | degree = rand.Intn(maxDegree + 1) 718 | leadingSame = rand.Intn(degree + 1) 719 | polyutil.SetRandomPolynomial(&a, degree) 720 | 721 | b = b[:len(a)] 722 | for i := range b { 723 | if i < degree+1-leadingSame { 724 | // lower coefficients are random but different to a 725 | b[i] = secp256k1.RandomFn() 726 | for b[i].Eq(&a[i]) { 727 | b[i] = secp256k1.RandomFn() 728 | } 729 | } else { 730 | // upper leadingSame coefficients are the same as a 731 | b[i] = a[i] 732 | } 733 | } 734 | 735 | c.Sub(a, b) 736 | 737 | // Check the degree 738 | Expect(subCheckDegree(a, b, c)).To(BeTrue()) 739 | } 740 | }) 741 | 742 | It("should work when the first argument is an alias of the caller", func() { 743 | trials := 1000 744 | maxDegree := 20 745 | 746 | var degreeA, degreeB int 747 | 748 | a := NewWithCapacity(maxDegree + 1) 749 | b := NewWithCapacity(maxDegree + 1) 750 | c := NewWithCapacity(maxDegree + 1) 751 | 752 | for i := 0; i < trials; i++ { 753 | degreeA = rand.Intn(maxDegree + 1) 754 | degreeB = rand.Intn(maxDegree + 1) 755 | polyutil.SetRandomPolynomial(&a, degreeA) 756 | polyutil.SetRandomPolynomial(&b, degreeB) 757 | c.Sub(a, b) 758 | a.Sub(a, b) 759 | 760 | Expect(a.Eq(c)).To(BeTrue()) 761 | } 762 | }) 763 | 764 | It("should work when the second argument is an alias of the caller", func() { 765 | trials := 1000 766 | maxDegree := 20 767 | 768 | var degreeA, degreeB int 769 | 770 | a := NewWithCapacity(maxDegree + 1) 771 | b := NewWithCapacity(maxDegree + 1) 772 | c := NewWithCapacity(maxDegree + 1) 773 | 774 | for i := 0; i < trials; i++ { 775 | degreeA = rand.Intn(maxDegree + 1) 776 | degreeB = rand.Intn(maxDegree + 1) 777 | polyutil.SetRandomPolynomial(&a, degreeA) 778 | polyutil.SetRandomPolynomial(&b, degreeB) 779 | c.Sub(a, b) 780 | b.Sub(a, b) 781 | 782 | Expect(b.Eq(c)).To(BeTrue()) 783 | } 784 | }) 785 | 786 | It("should work when the first argument is an alias of the second", func() { 787 | trials := 1000 788 | maxDegree := 20 789 | 790 | var degree int 791 | 792 | a := NewWithCapacity(maxDegree + 1) 793 | b := NewWithCapacity(maxDegree + 1) 794 | c := NewWithCapacity(maxDegree + 1) 795 | d := NewWithCapacity(maxDegree + 1) 796 | 797 | for i := 0; i < trials; i++ { 798 | degree = rand.Intn(maxDegree + 1) 799 | polyutil.SetRandomPolynomial(&a, degree) 800 | b.Set(a) 801 | c.Sub(a, b) 802 | d.Sub(a, a) 803 | 804 | Expect(d.Eq(c)).To(BeTrue()) 805 | } 806 | }) 807 | 808 | It("should work when the caller and both arguments are aliases of each other", func() { 809 | trials := 1000 810 | maxDegree := 20 811 | 812 | var degree int 813 | 814 | a := NewWithCapacity(maxDegree + 1) 815 | b := NewWithCapacity(maxDegree + 1) 816 | c := NewWithCapacity(maxDegree + 1) 817 | 818 | for i := 0; i < trials; i++ { 819 | degree = rand.Intn(maxDegree + 1) 820 | polyutil.SetRandomPolynomial(&a, degree) 821 | b.Set(a) 822 | c.Sub(a, b) 823 | a.Sub(a, a) 824 | 825 | Expect(a.Eq(c)).To(BeTrue()) 826 | } 827 | }) 828 | }) 829 | 830 | Context("when negating polynomials", func() { 831 | It("should satisfy the defining relation", func() { 832 | trials := 1000 833 | maxDegree := 20 834 | 835 | var neg secp256k1.Fn 836 | var degree int 837 | 838 | a := NewWithCapacity(maxDegree + 1) 839 | b := NewWithCapacity(maxDegree + 1) 840 | 841 | for i := 0; i < trials; i++ { 842 | degree = rand.Intn(maxDegree + 1) 843 | polyutil.SetRandomPolynomial(&a, degree) 844 | b.Neg(a) 845 | 846 | for j := 0; j <= b.Degree(); j++ { 847 | neg.Negate(a.Coefficient(j)) 848 | Expect(b.Coefficient(j).Eq(&neg)).To(BeTrue()) 849 | } 850 | } 851 | }) 852 | }) 853 | 854 | Context("when multiplying polynomials", func() { 855 | It("should satisfy the defining relation", func() { 856 | trials := 1000 857 | maxDegree := 20 858 | 859 | var term secp256k1.Fn 860 | var degreeA, degreeB int 861 | 862 | a := NewWithCapacity(maxDegree + 1) 863 | b := NewWithCapacity(maxDegree + 1) 864 | c := NewWithCapacity(2 * (maxDegree + 1)) 865 | d := NewWithCapacity(2 * (maxDegree + 1)) 866 | 867 | for i := 0; i < trials; i++ { 868 | degreeA = rand.Intn(maxDegree + 1) 869 | degreeB = rand.Intn(maxDegree + 1) 870 | polyutil.SetRandomPolynomial(&a, degreeA) 871 | polyutil.SetRandomPolynomial(&b, degreeB) 872 | c.Mul(a, b) 873 | 874 | // Manually calculate the multiplication 875 | d = d[:degreeA+degreeB+1] 876 | for j := 0; j <= d.Degree(); j++ { 877 | *d.Coefficient(j) = zero 878 | } 879 | for j := 0; j <= a.Degree(); j++ { 880 | for k := 0; k <= b.Degree(); k++ { 881 | term.Mul(a.Coefficient(j), b.Coefficient(k)) 882 | d.Coefficient(j+k).Add(d.Coefficient(j+k), &term) 883 | } 884 | } 885 | 886 | Expect(c.Eq(d)).To(BeTrue()) 887 | } 888 | }) 889 | 890 | It("should work when either argument is the zero polynomial", func() { 891 | trials := 1000 892 | maxDegree := 20 893 | 894 | var degree int 895 | 896 | a := NewWithCapacity(maxDegree + 1) 897 | b := NewWithCapacity(maxDegree + 1) 898 | z := NewWithCapacity(1) 899 | z.Zero() 900 | 901 | for i := 0; i < trials; i++ { 902 | degree = rand.Intn(maxDegree + 1) 903 | polyutil.SetRandomPolynomial(&a, degree) 904 | 905 | b.Mul(a, z) 906 | Expect(b.IsZero()).To(BeTrue()) 907 | 908 | b.Mul(z, a) 909 | Expect(b.IsZero()).To(BeTrue()) 910 | } 911 | }) 912 | 913 | It("should work when the first argument is an alias of the caller", func() { 914 | trials := 1000 915 | maxDegree := 20 916 | 917 | var degreeA, degreeB int 918 | 919 | a := NewWithCapacity(2 * (maxDegree + 1)) 920 | b := NewWithCapacity(maxDegree + 1) 921 | c := NewWithCapacity(2 * (maxDegree + 1)) 922 | 923 | for i := 0; i < trials; i++ { 924 | degreeA = rand.Intn(maxDegree + 1) 925 | degreeB = rand.Intn(maxDegree + 1) 926 | polyutil.SetRandomPolynomial(&a, degreeA) 927 | polyutil.SetRandomPolynomial(&b, degreeB) 928 | c.Mul(a, b) 929 | a.Mul(a, b) 930 | 931 | Expect(a.Eq(c)).To(BeTrue()) 932 | } 933 | }) 934 | 935 | It("should work when the second argument is an alias of the caller", func() { 936 | trials := 1000 937 | maxDegree := 20 938 | 939 | var degreeA, degreeB int 940 | 941 | a := NewWithCapacity(maxDegree + 1) 942 | b := NewWithCapacity(2 * (maxDegree + 1)) 943 | c := NewWithCapacity(2 * (maxDegree + 1)) 944 | 945 | for i := 0; i < trials; i++ { 946 | degreeA = rand.Intn(maxDegree + 1) 947 | degreeB = rand.Intn(maxDegree + 1) 948 | polyutil.SetRandomPolynomial(&a, degreeA) 949 | polyutil.SetRandomPolynomial(&b, degreeB) 950 | c.Mul(a, b) 951 | b.Mul(a, b) 952 | 953 | Expect(b.Eq(c)).To(BeTrue()) 954 | } 955 | }) 956 | 957 | It("should work when the first argument is an alias of the second", func() { 958 | trials := 1000 959 | maxDegree := 20 960 | 961 | var degree int 962 | 963 | a := NewWithCapacity(maxDegree + 1) 964 | b := NewWithCapacity(maxDegree + 1) 965 | c := NewWithCapacity(2 * (maxDegree + 1)) 966 | d := NewWithCapacity(2 * (maxDegree + 1)) 967 | 968 | for i := 0; i < trials; i++ { 969 | degree = rand.Intn(maxDegree + 1) 970 | polyutil.SetRandomPolynomial(&a, degree) 971 | b.Set(a) 972 | c.Mul(a, b) 973 | d.Mul(a, a) 974 | 975 | Expect(d.Eq(c)).To(BeTrue()) 976 | } 977 | }) 978 | 979 | It("should give an incorrect result when the caller and both arguments are aliases of each other", func() { 980 | trials := 1000 981 | maxDegree := 20 982 | 983 | var degree int 984 | 985 | a := NewWithCapacity(2 * (maxDegree + 1)) 986 | b := NewWithCapacity(maxDegree + 1) 987 | c := NewWithCapacity(2 * (maxDegree + 1)) 988 | 989 | for i := 0; i < trials; i++ { 990 | degree = rand.Intn(maxDegree + 1) 991 | polyutil.SetRandomPolynomial(&a, degree) 992 | b.Set(a) 993 | c.Mul(a, b) 994 | a.Mul(a, a) 995 | 996 | if degree == 0 { 997 | Expect(a.Eq(c)).To(BeTrue()) 998 | } else { 999 | Expect(a.Eq(c)).To(BeFalse()) 1000 | } 1001 | } 1002 | }) 1003 | }) 1004 | 1005 | Context("when doing polynomial division", func() { 1006 | Specify("the defining relation should hold", func() { 1007 | trials := 1000 1008 | maxDegree := 20 1009 | 1010 | var degreeA, degreeB int 1011 | 1012 | a := NewWithCapacity(maxDegree + 1) 1013 | b := NewWithCapacity(maxDegree + 1) 1014 | q := NewWithCapacity(maxDegree + 1) 1015 | r := NewWithCapacity(maxDegree + 1) 1016 | aRecon := NewWithCapacity(maxDegree + 1) 1017 | 1018 | for i := 0; i < trials; i++ { 1019 | degreeA = rand.Intn(maxDegree + 1) 1020 | degreeB = rand.Intn(degreeA + 1) 1021 | polyutil.SetRandomPolynomial(&a, degreeA) 1022 | polyutil.SetRandomPolynomial(&b, degreeB) 1023 | if b.IsZero() { 1024 | continue 1025 | } 1026 | 1027 | Divide(a, b, &q, &r) 1028 | aRecon.Mul(q, b) 1029 | aRecon.Add(aRecon, r) 1030 | 1031 | Expect(aRecon.Eq(a)).To(BeTrue()) 1032 | } 1033 | }) 1034 | 1035 | It("should give the trivial result when deg(a) < deb(b)", func() { 1036 | trials := 1000 1037 | maxDegree := 20 1038 | 1039 | var degreeA, degreeB int 1040 | 1041 | a := NewWithCapacity(maxDegree + 1) 1042 | b := NewWithCapacity(maxDegree + 1) 1043 | q := NewWithCapacity(maxDegree + 1) 1044 | r := NewWithCapacity(maxDegree + 1) 1045 | 1046 | for i := 0; i < trials; i++ { 1047 | degreeB = rand.Intn(maxDegree-1) + 2 1048 | degreeA = rand.Intn(degreeB-1) + 1 1049 | polyutil.SetRandomPolynomial(&a, degreeA) 1050 | polyutil.SetRandomPolynomial(&b, degreeB) 1051 | if b.IsZero() { 1052 | continue 1053 | } 1054 | 1055 | Divide(a, b, &q, &r) 1056 | 1057 | Expect(q.IsZero()).To(BeTrue()) 1058 | Expect(r.Eq(a)).To(BeTrue()) 1059 | } 1060 | }) 1061 | }) 1062 | }) 1063 | 1064 | func addCheckDegree(a, b, c Poly) bool { 1065 | degreeA, degreeB := a.Degree(), b.Degree() 1066 | var coeff secp256k1.Fn 1067 | if degreeA != degreeB { 1068 | return c.Degree() == shamirutil.Max(degreeA, degreeB) 1069 | } 1070 | 1071 | // Account for the case that some leading coefficients 1072 | // cancelled eachother 1073 | if c.Degree() != degreeA { 1074 | for i := c.Degree() + 1; i <= degreeA; i++ { 1075 | coeff.Add(&a[i], &b[i]) 1076 | if !coeff.IsZero() { 1077 | return false 1078 | } 1079 | } 1080 | 1081 | return true 1082 | } 1083 | 1084 | return c.Degree() == degreeA 1085 | } 1086 | 1087 | func subCheckDegree(a, b, c Poly) bool { 1088 | degreeA, degreeB := a.Degree(), b.Degree() 1089 | if degreeA != degreeB { 1090 | return c.Degree() == shamirutil.Max(degreeA, degreeB) 1091 | } 1092 | 1093 | // Account for the case that some leading coefficients 1094 | // cancelled eachother 1095 | if c.Degree() != degreeA { 1096 | for i := c.Degree() + 1; i <= degreeA; i++ { 1097 | if !a.Coefficient(i).Eq(b.Coefficient(i)) { 1098 | return false 1099 | } 1100 | } 1101 | 1102 | return true 1103 | } 1104 | 1105 | return c.Degree() == degreeA 1106 | } 1107 | 1108 | func BenchmarkPolyAdd(b *testing.B) { 1109 | n := 100 1110 | poly1 := NewWithCapacity(n) 1111 | poly2 := NewWithCapacity(n) 1112 | polySum := NewWithCapacity(n) 1113 | 1114 | polyutil.SetRandomPolynomial(&poly1, n-1) 1115 | polyutil.SetRandomPolynomial(&poly2, n-1) 1116 | 1117 | b.ResetTimer() 1118 | for i := 0; i < b.N; i++ { 1119 | polySum.Add(poly1, poly2) 1120 | } 1121 | } 1122 | 1123 | func BenchmarkPolyMul(b *testing.B) { 1124 | n := 50 1125 | poly1 := NewWithCapacity(n) 1126 | poly2 := NewWithCapacity(n) 1127 | polyProd := NewWithCapacity(2 * n) 1128 | 1129 | polyutil.SetRandomPolynomial(&poly1, n-1) 1130 | polyutil.SetRandomPolynomial(&poly2, n-1) 1131 | 1132 | b.ResetTimer() 1133 | for i := 0; i < b.N; i++ { 1134 | polyProd.Mul(poly1, poly2) 1135 | } 1136 | } 1137 | 1138 | func BenchmarkPolyInterpolate(b *testing.B) { 1139 | n := 100 1140 | indices := shamirutil.RandomIndices(n) 1141 | values := shamirutil.RandomIndices(n) 1142 | poly := NewWithCapacity(n) 1143 | interp := NewInterpolator(indices) 1144 | 1145 | b.ResetTimer() 1146 | for i := 0; i < b.N; i++ { 1147 | interp.Interpolate(values, &poly) 1148 | } 1149 | } 1150 | -------------------------------------------------------------------------------- /poly/polyutil/polyutil.go: -------------------------------------------------------------------------------- 1 | package polyutil 2 | 3 | import ( 4 | "github.com/renproject/secp256k1" 5 | "github.com/renproject/shamir/poly" 6 | ) 7 | 8 | // SetRandomPolynomial sets the given polynomial to be a random polynomial with 9 | // the given degree. 10 | func SetRandomPolynomial(dst *poly.Poly, degree int) { 11 | // Make all memory available to be accessed. 12 | *dst = (*dst)[:cap(*dst)] 13 | 14 | // Fill entire memory with random values, as even memory locations 15 | // beyond the degree can contain non zero values in practice. 16 | for i := range *dst { 17 | (*dst)[i] = secp256k1.RandomFn() 18 | } 19 | 20 | // Ensure that the leading term is non-zero. 21 | for dst.Coefficient(degree).IsZero() { 22 | (*dst)[degree] = secp256k1.RandomFn() 23 | } 24 | 25 | // Set degree. 26 | *dst = (*dst)[:degree+1] 27 | } 28 | -------------------------------------------------------------------------------- /rs/marshal.go: -------------------------------------------------------------------------------- 1 | package rs 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/secp256k1" 8 | "github.com/renproject/shamir/eea" 9 | "github.com/renproject/shamir/poly" 10 | "github.com/renproject/surge" 11 | ) 12 | 13 | // Generate implements the quick.Generator interface. 14 | func (dec Decoder) Generate(rand *rand.Rand, size int) reflect.Value { 15 | n := rand.Int31() 16 | k := rand.Int31() 17 | indices := make([]secp256k1.Fn, size/10) 18 | for i := range indices { 19 | indices[i] = secp256k1.RandomFn() 20 | } 21 | interpolator := poly.Interpolator{}.Generate(rand, size).Interface().(poly.Interpolator) 22 | eea := eea.Stepper{}.Generate(rand, size).Interface().(eea.Stepper) 23 | g0 := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 24 | interpPoly := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 25 | f1 := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 26 | r := poly.Poly{}.Generate(rand, size).Interface().(poly.Poly) 27 | errors := make([]secp256k1.Fn, size/10) 28 | for i := range errors { 29 | errors[i] = secp256k1.RandomFn() 30 | } 31 | errorsComputed := rand.Int()&1 == 1 32 | decoder := Decoder{ 33 | n: int(n), k: int(k), 34 | indices: indices, 35 | interpolator: interpolator, 36 | eea: eea, 37 | 38 | g0: g0, 39 | interpPoly: interpPoly, 40 | f1: f1, r: r, 41 | errors: errors, 42 | errorsComputed: errorsComputed, 43 | } 44 | return reflect.ValueOf(decoder) 45 | } 46 | 47 | // SizeHint implements the surge.SizeHinter interface. 48 | func (dec Decoder) SizeHint() int { 49 | return surge.SizeHintI32 + 50 | surge.SizeHintI32 + 51 | surge.SizeHint(dec.indices) + 52 | dec.interpolator.SizeHint() + 53 | dec.eea.SizeHint() + 54 | dec.g0.SizeHint() + 55 | dec.interpPoly.SizeHint() + 56 | dec.f1.SizeHint() + 57 | dec.r.SizeHint() + 58 | surge.SizeHint(dec.errors) + 59 | surge.SizeHint(dec.errorsComputed) 60 | } 61 | 62 | // Marshal implements the surge.Marshaler interface. 63 | func (dec Decoder) Marshal(buf []byte, rem int) ([]byte, int, error) { 64 | buf, rem, err := surge.MarshalI32(int32(dec.n), buf, rem) 65 | if err != nil { 66 | return buf, rem, err 67 | } 68 | buf, rem, err = surge.MarshalI32(int32(dec.k), buf, rem) 69 | if err != nil { 70 | return buf, rem, err 71 | } 72 | 73 | buf, rem, err = surge.Marshal(dec.indices, buf, rem) 74 | if err != nil { 75 | return buf, rem, err 76 | } 77 | 78 | buf, rem, err = dec.interpolator.Marshal(buf, rem) 79 | if err != nil { 80 | return buf, rem, err 81 | } 82 | buf, rem, err = dec.eea.Marshal(buf, rem) 83 | if err != nil { 84 | return buf, rem, err 85 | } 86 | buf, rem, err = dec.g0.Marshal(buf, rem) 87 | if err != nil { 88 | return buf, rem, err 89 | } 90 | buf, rem, err = dec.interpPoly.Marshal(buf, rem) 91 | if err != nil { 92 | return buf, rem, err 93 | } 94 | buf, rem, err = dec.f1.Marshal(buf, rem) 95 | if err != nil { 96 | return buf, rem, err 97 | } 98 | buf, rem, err = dec.r.Marshal(buf, rem) 99 | if err != nil { 100 | return buf, rem, err 101 | } 102 | buf, rem, err = surge.Marshal(dec.errors, buf, rem) 103 | if err != nil { 104 | return buf, rem, err 105 | } 106 | return surge.Marshal(dec.errorsComputed, buf, rem) 107 | } 108 | 109 | // Unmarshal implements the surge.Unmarshaler interface. 110 | func (dec *Decoder) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 111 | var tmp int32 112 | buf, rem, err := surge.UnmarshalI32(&tmp, buf, rem) 113 | if err != nil { 114 | return buf, rem, err 115 | } 116 | dec.n = int(tmp) 117 | buf, rem, err = surge.UnmarshalI32(&tmp, buf, rem) 118 | if err != nil { 119 | return buf, rem, err 120 | } 121 | dec.k = int(tmp) 122 | buf, rem, err = surge.Unmarshal(&dec.indices, buf, rem) 123 | if err != nil { 124 | return buf, rem, err 125 | } 126 | buf, rem, err = dec.interpolator.Unmarshal(buf, rem) 127 | if err != nil { 128 | return buf, rem, err 129 | } 130 | buf, rem, err = dec.eea.Unmarshal(buf, rem) 131 | if err != nil { 132 | return buf, rem, err 133 | } 134 | buf, rem, err = dec.g0.Unmarshal(buf, rem) 135 | if err != nil { 136 | return buf, rem, err 137 | } 138 | buf, rem, err = dec.interpPoly.Unmarshal(buf, rem) 139 | if err != nil { 140 | return buf, rem, err 141 | } 142 | buf, rem, err = dec.f1.Unmarshal(buf, rem) 143 | if err != nil { 144 | return buf, rem, err 145 | } 146 | buf, rem, err = dec.r.Unmarshal(buf, rem) 147 | if err != nil { 148 | return buf, rem, err 149 | } 150 | buf, rem, err = surge.Unmarshal(&dec.errors, buf, rem) 151 | if err != nil { 152 | return buf, rem, err 153 | } 154 | return surge.Unmarshal(&dec.errorsComputed, buf, rem) 155 | } 156 | -------------------------------------------------------------------------------- /rs/marshal_test.go: -------------------------------------------------------------------------------- 1 | package rs_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/renproject/surge/surgeutil" 8 | 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | . "github.com/renproject/shamir/rs" 12 | ) 13 | 14 | var _ = Describe("Surge marshalling", func() { 15 | trials := 10 16 | t := reflect.TypeOf(Decoder{}) 17 | 18 | Context(fmt.Sprintf("surge marshalling and unmarshalling for %v", t), func() { 19 | It("should be the same after marshalling and unmarshalling", func() { 20 | for i := 0; i < trials; i++ { 21 | Expect(surgeutil.MarshalUnmarshalCheck(t)).To(Succeed()) 22 | } 23 | }) 24 | 25 | It("should not panic when fuzzing", func() { 26 | for i := 0; i < trials; i++ { 27 | Expect(func() { surgeutil.Fuzz(t) }).ToNot(Panic()) 28 | } 29 | }) 30 | 31 | Context("marshalling", func() { 32 | It("should return an error when the buffer is too small", func() { 33 | for i := 0; i < trials; i++ { 34 | Expect(surgeutil.MarshalBufTooSmall(t)).To(Succeed()) 35 | } 36 | }) 37 | 38 | It("should return an error when the memory quota is too small", func() { 39 | for i := 0; i < trials; i++ { 40 | Expect(surgeutil.MarshalRemTooSmall(t)).To(Succeed()) 41 | } 42 | }) 43 | }) 44 | 45 | Context("unmarshalling", func() { 46 | It("should return an error when the buffer is too small", func() { 47 | for i := 0; i < trials; i++ { 48 | Expect(surgeutil.UnmarshalBufTooSmall(t)).To(Succeed()) 49 | } 50 | }) 51 | 52 | It("should return an error when the memory quota is too small", func() { 53 | for i := 0; i < trials; i++ { 54 | Expect(surgeutil.UnmarshalRemTooSmall(t)).To(Succeed()) 55 | } 56 | }) 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /rs/rs.go: -------------------------------------------------------------------------------- 1 | package rs 2 | 3 | import ( 4 | "github.com/renproject/secp256k1" 5 | "github.com/renproject/shamir/eea" 6 | "github.com/renproject/shamir/poly" 7 | ) 8 | 9 | // Decoder can do Reed-Solomon decoding on given codewords. RS code words are 10 | // points on some unknown polynomial. The x coordinates of these points are 11 | // called the indices. Each instance of a decoder corresponds to a specific set 12 | // of indices; this allows multiple decodings to use the same relatively 13 | // expensive setup. 14 | type Decoder struct { 15 | n, k int 16 | indices []secp256k1.Fn 17 | interpolator poly.Interpolator 18 | eea eea.Stepper 19 | 20 | g0 poly.Poly 21 | interpPoly poly.Poly 22 | f1, r poly.Poly 23 | errors []secp256k1.Fn 24 | errorsComputed bool 25 | } 26 | 27 | // NewDecoder constructs a new decoder instance from a given set of indices 28 | // where k is the maximum degree of the polynomial for the codewords. This 29 | // decoder can be used to decode any codeword that has the same set of indices. 30 | // That is, if the decoder is constructed from indices `{x0, x1, ..., xn}`, 31 | // then for a codeword `{y0, y1, ..., yn}` the decoder will attempt to find a 32 | // polynomial `f` such that `f(xi) = yi` for as many `i` as possible. The 33 | // number of points that constitute a codeword, `n`, is determined by the 34 | // length of the index slice. The values in the index slice are copied, and so 35 | // are safe to modify after being given to this constructor. 36 | func NewDecoder(inds []secp256k1.Fn, k int) Decoder { 37 | indices := make([]secp256k1.Fn, len(inds)) 38 | copy(indices, inds) 39 | n := len(indices) 40 | interpolator := poly.NewInterpolator(indices) 41 | eea := eea.NewStepperWithCapacity(n + 1) 42 | g0 := poly.NewWithCapacity(n + 1) 43 | interpPoly := poly.NewWithCapacity(n) 44 | f1 := poly.NewWithCapacity(n) 45 | r := poly.NewWithCapacity(n) 46 | errors := make([]secp256k1.Fn, k) 47 | errorsComputed := false 48 | 49 | // Construct g0 50 | g0[0].SetU16(1) 51 | linearTerm := poly.NewWithCapacity(2) 52 | linearTerm = linearTerm[:2] 53 | for i := range indices { 54 | *linearTerm.Coefficient(0) = inds[i] 55 | linearTerm.Coefficient(0).Negate(linearTerm.Coefficient(0)) 56 | linearTerm.Coefficient(1).SetU16(1) 57 | g0.Mul(g0, linearTerm) 58 | } 59 | 60 | return Decoder{ 61 | n: n, k: k, 62 | indices: indices, 63 | interpolator: interpolator, 64 | eea: eea, 65 | 66 | g0: g0, 67 | interpPoly: interpPoly, 68 | f1: f1, r: r, 69 | errors: errors, 70 | errorsComputed: errorsComputed, 71 | } 72 | } 73 | 74 | // Decode executes the RS decoding algorithm to try to recover the encoded 75 | // polynomial. If decoding was successful, the polynomial is returned and the 76 | // returned boolean is true. Otherwise, the polynomial is nil and the boolean 77 | // is false. Decoding will fail if there are more than (n - k)/2 errors, but 78 | // less than n - k. If there are more than n - k errors, the output behaviour 79 | // is undefined. 80 | func (dec *Decoder) Decode(values []secp256k1.Fn) (*poly.Poly, bool) { 81 | threshold := (dec.n + dec.k) / 2 82 | dec.errorsComputed = false 83 | 84 | // Interpolate 85 | dec.interpolator.Interpolate(values, &dec.interpPoly) 86 | 87 | // Partial GCD 88 | dec.eea.Init(dec.g0, dec.interpPoly) 89 | for dec.eea.Rem().Degree() >= threshold { 90 | dec.eea.Step() 91 | } 92 | 93 | // Long division 94 | poly.Divide(*dec.eea.Rem(), *dec.eea.T(), &dec.f1, &dec.r) 95 | 96 | if dec.r.IsZero() && dec.f1.Degree() < dec.k { 97 | return &dec.f1, true 98 | } 99 | return nil, false 100 | } 101 | 102 | // ErrorIndices returns a slice of indices that correspond to the error 103 | // locations for the most recent execution of the decoding algorithm. If the 104 | // decoding algorithm has not been run yet or there are no errors, a nil slice 105 | // will be returned. 106 | func (dec *Decoder) ErrorIndices() []secp256k1.Fn { 107 | // If we have already computed the errors for this decoding, short ciruit 108 | // and yield the cached result. 109 | if dec.errorsComputed { 110 | return dec.errors 111 | } 112 | 113 | // In this case the decoding algorithm has not yet been run. 114 | if dec.eea.T().IsZero() { 115 | return nil 116 | } 117 | 118 | var value secp256k1.Fn 119 | dec.errorsComputed = true 120 | dec.errors = dec.errors[:0] 121 | 122 | for _, index := range dec.indices { 123 | value = dec.eea.T().Evaluate(index) 124 | if value.IsZero() { 125 | dec.errors = append(dec.errors, index) 126 | } 127 | } 128 | 129 | if len(dec.errors) == 0 { 130 | return nil 131 | } 132 | 133 | return dec.errors 134 | } 135 | -------------------------------------------------------------------------------- /rs/rs_suite_test.go: -------------------------------------------------------------------------------- 1 | package rs_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestRs(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Rs Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rs/rs_test.go: -------------------------------------------------------------------------------- 1 | package rs_test 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/renproject/secp256k1" 8 | "github.com/renproject/shamir/eea/eeautil" 9 | "github.com/renproject/shamir/poly" 10 | "github.com/renproject/shamir/poly/polyutil" 11 | "github.com/renproject/shamir/shamirutil" 12 | "github.com/renproject/surge" 13 | 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | . "github.com/renproject/shamir/rs" 17 | ) 18 | 19 | var _ = Describe("Reed-Solomon Decoding", func() { 20 | Context("when decoding messages", func() { 21 | It("should recover the polynomial when there are no errors", func() { 22 | trials := 100 23 | maxN := 20 24 | maxDegree := maxN - 1 25 | var n, k int 26 | 27 | poly := poly.NewWithCapacity(maxDegree + 1) 28 | values := make([]secp256k1.Fn, maxN) 29 | 30 | for i := 0; i < trials; i++ { 31 | n = rand.Intn(maxN) + 1 32 | k = rand.Intn(n) + 1 33 | indices := shamirutil.RandomIndices(n) 34 | decoder := NewDecoder(indices, k) 35 | 36 | // Marshal and then unmarshal the decoder before using it, to 37 | // check that no crucial information is lost during this 38 | // process. 39 | decoderData, err := surge.ToBinary(decoder) 40 | Expect(err).ToNot(HaveOccurred()) 41 | decoder = Decoder{} 42 | Expect(surge.FromBinary(&decoder, decoderData)).To(Succeed()) 43 | 44 | polyutil.SetRandomPolynomial(&poly, k-1) 45 | values = values[:n] 46 | for j, index := range indices { 47 | values[j] = poly.Evaluate(index) 48 | } 49 | reconstructed, ok := decoder.Decode(values) 50 | errors := decoder.ErrorIndices() 51 | 52 | Expect(ok).To(BeTrue()) 53 | Expect(reconstructed.Eq(poly)).To(BeTrue()) 54 | Expect(errors).To(BeNil()) 55 | } 56 | }) 57 | 58 | It("should recover the polynomial when there are fewer than t errors", func() { 59 | trials := 100 60 | maxN := 20 61 | maxDegree := maxN - 1 62 | var n, k, t int 63 | 64 | poly := poly.NewWithCapacity(maxDegree + 1) 65 | values := make([]secp256k1.Fn, maxN) 66 | l := make([]int, maxN/2) 67 | 68 | for i := 0; i < trials; i++ { 69 | n = rand.Intn(maxN-2) + 3 70 | k = rand.Intn(n-2) + 1 71 | t = (n - k) / 2 72 | indices := shamirutil.RandomIndices(n) 73 | decoder := NewDecoder(indices, k) 74 | 75 | // Marshal and then unmarshal the decoder before using it, to 76 | // check that no crucial information is lost during this 77 | // process. 78 | decoderData, err := surge.ToBinary(decoder) 79 | Expect(err).ToNot(HaveOccurred()) 80 | decoder = Decoder{} 81 | Expect(surge.FromBinary(&decoder, decoderData)).To(Succeed()) 82 | 83 | polyutil.SetRandomPolynomial(&poly, k-1) 84 | values = values[:n] 85 | for j, index := range indices { 86 | values[j] = poly.Evaluate(index) 87 | } 88 | 89 | // Add errors to the values 90 | e := rand.Intn(t) + 1 91 | eeautil.RandomSubset(&l, e, n) 92 | addErrors(values[:], l) 93 | 94 | reconstructed, ok := decoder.Decode(values[:]) 95 | 96 | Expect(ok).To(BeTrue()) 97 | Expect(reconstructed.Eq(poly)).To(BeTrue()) 98 | 99 | // The errors should be correctly identified 100 | Expect(len(decoder.ErrorIndices())).To(Equal(e)) 101 | for _, index := range decoder.ErrorIndices() { 102 | listIndex := 0 103 | for !indices[listIndex].Eq(&index) { 104 | listIndex++ 105 | } 106 | Expect(eeautil.Contains(l, listIndex)).To(BeTrue()) 107 | } 108 | } 109 | }) 110 | 111 | It("should not recover the polynomial when there are more than t errors", func() { 112 | trials := 100 113 | maxN := 20 114 | maxDegree := maxN - 1 115 | var n, k, t int 116 | 117 | poly := poly.NewWithCapacity(maxDegree + 1) 118 | values := make([]secp256k1.Fn, maxN) 119 | l := make([]int, maxN/2) 120 | 121 | for i := 0; i < trials; i++ { 122 | n = rand.Intn(maxN-2) + 3 123 | k = rand.Intn(n-2) + 1 124 | t = (n - k) / 2 125 | indices := shamirutil.RandomIndices(n) 126 | decoder := NewDecoder(indices, k) 127 | 128 | // Marshal and then unmarshal the decoder before using it, to 129 | // check that no crucial information is lost during this 130 | // process. 131 | decoderData, err := surge.ToBinary(decoder) 132 | Expect(err).ToNot(HaveOccurred()) 133 | decoder = Decoder{} 134 | Expect(surge.FromBinary(&decoder, decoderData)).To(Succeed()) 135 | 136 | polyutil.SetRandomPolynomial(&poly, k-1) 137 | values = values[:n] 138 | for j, index := range indices { 139 | values[j] = poly.Evaluate(index) 140 | } 141 | 142 | // Add errors to the values 143 | e := rand.Intn(n-t) + 1 + t 144 | eeautil.RandomSubset(&l, e, n) 145 | addErrors(values[:], l) 146 | 147 | reconstructed, ok := decoder.Decode(values[:]) 148 | 149 | Expect(ok).To(BeFalse()) 150 | Expect(reconstructed).To(BeNil()) 151 | } 152 | }) 153 | 154 | It("should return a nil error slice when nothing has been decoded", func() { 155 | const n int = 15 156 | const k int = 6 157 | 158 | indices := [n]secp256k1.Fn{} 159 | 160 | for i := range indices { 161 | indices[i].SetU16(uint16(i + 1)) 162 | } 163 | 164 | decoder := NewDecoder(indices[:], k) 165 | 166 | // Marshal and then unmarshal the decoder before using it, to 167 | // check that no crucial information is lost during this 168 | // process. 169 | decoderData, err := surge.ToBinary(decoder) 170 | Expect(err).ToNot(HaveOccurred()) 171 | decoder = Decoder{} 172 | Expect(surge.FromBinary(&decoder, decoderData)).To(Succeed()) 173 | 174 | Expect(decoder.ErrorIndices()).To(BeNil()) 175 | }) 176 | }) 177 | }) 178 | 179 | func BenchmarkDecodeNoErrors(b *testing.B) { 180 | const n int = 100 181 | const k int = 34 182 | degree := k - 1 183 | 184 | poly := poly.NewWithCapacity(degree + 1) 185 | values := [n]secp256k1.Fn{} 186 | indices := [n]secp256k1.Fn{} 187 | 188 | for i := range indices { 189 | indices[i].SetU16(uint16(i + 1)) 190 | } 191 | polyutil.SetRandomPolynomial(&poly, degree) 192 | 193 | decoder := NewDecoder(indices[:], k) 194 | 195 | for j, index := range indices { 196 | values[j] = poly.Evaluate(index) 197 | } 198 | 199 | b.ResetTimer() 200 | for i := 0; i < b.N; i++ { 201 | _, _ = decoder.Decode(values[:]) 202 | } 203 | } 204 | 205 | func BenchmarkDecodeRecoverableErrors(b *testing.B) { 206 | const n int = 100 207 | const k int = 34 208 | t := (n - k) / 2 209 | degree := k - 1 210 | 211 | poly := poly.NewWithCapacity(degree + 1) 212 | values := [n]secp256k1.Fn{} 213 | indices := [n]secp256k1.Fn{} 214 | l := make([]int, t) 215 | 216 | for i := range indices { 217 | indices[i].SetU16(uint16(i + 1)) 218 | } 219 | polyutil.SetRandomPolynomial(&poly, degree) 220 | 221 | decoder := NewDecoder(indices[:], k) 222 | 223 | for j, index := range indices { 224 | values[j] = poly.Evaluate(index) 225 | } 226 | 227 | // Add errors to the values 228 | e := rand.Intn(t) + 1 229 | eeautil.RandomSubset(&l, e, n) 230 | addErrors(values[:], l) 231 | 232 | b.ResetTimer() 233 | for i := 0; i < b.N; i++ { 234 | _, _ = decoder.Decode(values[:]) 235 | } 236 | } 237 | 238 | func addErrors(values []secp256k1.Fn, subset []int) { 239 | for _, i := range subset { 240 | values[i] = secp256k1.RandomFn() 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /shamir.go: -------------------------------------------------------------------------------- 1 | package shamir 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/renproject/secp256k1" 7 | ) 8 | 9 | // ShareSize is the number of bytes in a share. 10 | const ShareSize = 2 * secp256k1.FnSizeMarshalled 11 | 12 | // Shares represents a slice of Shamir shares 13 | type Shares []Share 14 | 15 | // Share represents a single share in a Shamir secret sharing scheme. 16 | type Share struct { 17 | Index, Value secp256k1.Fn 18 | } 19 | 20 | // NewShare constructs a new Shamir share from an index and a value. 21 | func NewShare(index, value secp256k1.Fn) Share { 22 | return Share{Index: index, Value: value} 23 | } 24 | 25 | // Eq returns true if the two shares are equal, and false otherwise. 26 | func (s *Share) Eq(other *Share) bool { 27 | return s.Index.Eq(&other.Index) && s.Value.Eq(&other.Value) 28 | } 29 | 30 | // IndexEq returns true if the index of the two shares are equal, and false 31 | // otherwise. 32 | func (s *Share) IndexEq(other *secp256k1.Fn) bool { 33 | return s.Index.Eq(other) 34 | } 35 | 36 | // Add computes the addition of the two input shares and stores the result in 37 | // the caller. Addition is defined by adding the values but leaving the index 38 | // unchanged. 39 | // 40 | // Panics: Addition only makes sense when the two input shares have the same 41 | // index. If they do not, this function wil panic. 42 | func (s *Share) Add(a, b *Share) { 43 | if !a.Index.Eq(&b.Index) { 44 | panic(fmt.Sprintf( 45 | "cannot add shares with different indices: lhs has index %v and rhs has index %v", 46 | a.Index, 47 | b.Index, 48 | )) 49 | } 50 | 51 | s.Index = a.Index 52 | s.Value.Add(&a.Value, &b.Value) 53 | } 54 | 55 | // AddConstant computes the addition of the input share and the given constant 56 | // and stores the result in the caller. Addition is defined by adding the 57 | // constant to the share value but leaving the index unchanged. 58 | func (s *Share) AddConstant(other *Share, c *secp256k1.Fn) { 59 | s.Index = other.Index 60 | s.Value.Add(&other.Value, c) 61 | } 62 | 63 | // Scale multiplies the input share by a constant and then stores it in the 64 | // caller. This is defined as multiplying the share value by the scale, and 65 | // leaving the index unchanged. 66 | func (s *Share) Scale(other *Share, scale *secp256k1.Fn) { 67 | s.Index = other.Index 68 | s.Value.Mul(&other.Value, scale) 69 | } 70 | 71 | // ShareSecret creates Shamir shares for the given secret at the given 72 | // threshold, and stores them in the given destination slice. In the returned 73 | // Shares, there will be one share for each index in the indices that were used 74 | // to construct the Sharer. If k is larger than the number of indices, in which 75 | // case it would be impossible to reconstruct the secret, an error is returned. 76 | // 77 | // Panics: This function will panic if the destination shares slice has a 78 | // capacity less than n (the number of indices). 79 | func ShareSecret(dst *Shares, indices []secp256k1.Fn, secret secp256k1.Fn, k int) error { 80 | coeffs := make([]secp256k1.Fn, k) 81 | return ShareAndGetCoeffs(dst, coeffs, indices, secret, k) 82 | } 83 | 84 | // ShareAndGetCoeffs is the same as ShareSecret, but uses the provided slice to 85 | // store the generated coefficients of the sharing polynomial. If this function 86 | // successfully returns, this slice will contain the coefficients of the 87 | // sharing polynomial, where index 0 is the constant term. 88 | // 89 | // Panics: This function will panic if the destination shares slice has a 90 | // capacity less than n (the number of indices) or the coefficients slice has 91 | // length less than k, or any of the given indices is the zero element. 92 | func ShareAndGetCoeffs(dst *Shares, coeffs, indices []secp256k1.Fn, secret secp256k1.Fn, k int) error { 93 | for _, index := range indices { 94 | if index.IsZero() { 95 | panic("cannot create share for index zero") 96 | } 97 | } 98 | if k > len(indices) { 99 | return fmt.Errorf( 100 | "reconstruction threshold too large: expected k <= %v, got k = %v", 101 | len(indices), k, 102 | ) 103 | } 104 | setRandomCoeffs(coeffs, secret, k) 105 | 106 | // Set shares 107 | // NOTE: This panics if the destination slice does not have the required 108 | // capacity. 109 | *dst = (*dst)[:len(indices)] 110 | var eval secp256k1.Fn 111 | for i, ind := range indices { 112 | polyEval(&eval, &ind, coeffs) 113 | (*dst)[i].Index = ind 114 | (*dst)[i].Value = eval 115 | } 116 | 117 | return nil 118 | } 119 | 120 | // Sets the coefficients of the Sharer to represent a random degree k-1 121 | // polynomial with constant term equal to the given secret. 122 | // 123 | // Panics: This function will panic if k is greater than len(coeffs). 124 | func setRandomCoeffs(coeffs []secp256k1.Fn, secret secp256k1.Fn, k int) { 125 | coeffs = coeffs[:k] 126 | coeffs[0] = secret 127 | 128 | // NOTE: If k > len(coeffs), then this will panic when i > len(coeffs). 129 | for i := 1; i < k; i++ { 130 | coeffs[i] = secp256k1.RandomFn() 131 | } 132 | } 133 | 134 | // Evaluates the polynomial defined by the given coefficients at the point x 135 | // and stores the result in y. Modifies y, but leaves x and coeffs unchanged. 136 | // Normalizes y, so this this is not neccesary to do manually after calling 137 | // this function. 138 | // 139 | // Panics: This function assumes that len(coeffs) is at least 1 and not nil. If 140 | // it is not, it will panic. It does not make sense to call this function if 141 | // coeffs is the empty (or nil) slice. 142 | func polyEval(y, x *secp256k1.Fn, coeffs []secp256k1.Fn) { 143 | // NOTE: This will panic if len(coeffs) is less than 1 or if coeffs is nil. 144 | *y = coeffs[len(coeffs)-1] 145 | for i := len(coeffs) - 2; i >= 0; i-- { 146 | y.Mul(y, x) 147 | y.Add(y, &coeffs[i]) 148 | } 149 | } 150 | 151 | // Open computes the secret corresponding to the given shares. This is 152 | // equivalent to interpolating the polynomial that passes through the given 153 | // points, and returning the constant term. It is assumed that all shares have 154 | // different indices. Further properties that need to be hold if this function 155 | // is to correctly reconstruct the secret for a sharing with threshoold k are: 156 | // - There are at least k shares. 157 | // - All shares are valid, in the sense that they have not been maliciously 158 | // modified. 159 | func Open(shares Shares) secp256k1.Fn { 160 | var num, denom, res, tmp secp256k1.Fn 161 | res.SetU16(0) 162 | for i := range shares { 163 | num.SetU16(1) 164 | denom.SetU16(1) 165 | for j := range shares { 166 | if shares[i].Index.Eq(&shares[j].Index) { 167 | continue 168 | } 169 | tmp.Negate(&shares[i].Index) 170 | tmp.Add(&tmp, &shares[j].Index) 171 | denom.Mul(&denom, &tmp) 172 | num.Mul(&num, &shares[j].Index) 173 | } 174 | denom.Inverse(&denom) 175 | tmp.Mul(&num, &denom) 176 | tmp.Mul(&tmp, &shares[i].Value) 177 | res.Add(&res, &tmp) 178 | } 179 | return res 180 | } 181 | -------------------------------------------------------------------------------- /shamir_suite_test.go: -------------------------------------------------------------------------------- 1 | package shamir_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | func TestShamir(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Shamir Suite") 13 | } 14 | -------------------------------------------------------------------------------- /shamir_test.go: -------------------------------------------------------------------------------- 1 | package shamir_test 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | 8 | "github.com/renproject/secp256k1" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | . "github.com/renproject/shamir" 13 | . "github.com/renproject/shamir/shamirutil" 14 | ) 15 | 16 | // 17 | // Let n and k be given where n >= k. Then Shamir secret sharing with these 18 | // parameters should satisfy the following properties: 19 | // 20 | // 1. Any element in the field can be shared such that n shares are produced. 21 | // Further, any subset of k or more of these shares can be combined to 22 | // reconstruct the shared element (secret). 23 | // 24 | // 2. The shares are homomorphically additive. This means that if two secrets 25 | // are shared, then adding the respective shares (shares with the same index 26 | // get added) results in a new sharing, and the secret value for this sharing 27 | // is the sum of the two original secrets. 28 | // 29 | // 3. The shares are homomorphic with regard to scaling. This means that if a 30 | // secret is shared, then multiplying each share by the same scalar results in 31 | // a new sharing, and the secret value for this sharing is the product of the 32 | // original secret and the scalar. 33 | // 34 | var _ = Describe("Shamir Secret Sharing", func() { 35 | rand.Seed(time.Now().UnixNano()) 36 | 37 | Context("Sharing consistency (1)", func() { 38 | trials := 100 39 | n := 20 40 | 41 | var k int 42 | var secret secp256k1.Fn 43 | 44 | Specify("any qualified subset can reconstruct the secret correctly", func() { 45 | indices := RandomIndices(n) 46 | shares := make(Shares, n) 47 | 48 | for i := 0; i < trials; i++ { 49 | k = RandRange(1, n) 50 | secret = secp256k1.RandomFn() 51 | 52 | err := ShareSecret(&shares, indices, secret, k) 53 | Expect(err).ToNot(HaveOccurred()) 54 | 55 | recon := Open(shares) 56 | Expect(recon.Eq(&secret)).To(BeTrue()) 57 | 58 | Expect(SharesAreConsistent(shares, k)).To(BeTrue()) 59 | } 60 | }) 61 | }) 62 | 63 | Context("Homomorphic under addition (2)", func() { 64 | trials := 100 65 | n := 20 66 | 67 | var k1, k2, kmax int 68 | var secret1, secret2, secretSummed secp256k1.Fn 69 | 70 | Specify("summed shares should be a consistent sharing of the sum of the secrets", func() { 71 | indices := RandomIndices(n) 72 | shares1 := make(Shares, n) 73 | shares2 := make(Shares, n) 74 | sharesSummed := make(Shares, n) 75 | 76 | for i := 0; i < trials; i++ { 77 | k1 = RandRange(1, n) 78 | k2 = RandRange(1, n) 79 | kmax = Max(k1, k2) 80 | secret1 = secp256k1.RandomFn() 81 | secret2 = secp256k1.RandomFn() 82 | secretSummed.Add(&secret1, &secret2) 83 | 84 | _ = ShareSecret(&shares1, indices, secret1, k1) 85 | _ = ShareSecret(&shares2, indices, secret2, k2) 86 | 87 | // Construct the summed shares 88 | for i := range sharesSummed { 89 | sharesSummed[i].Add(&shares1[i], &shares2[i]) 90 | } 91 | 92 | recon := Open(sharesSummed) 93 | Expect(recon.Eq(&secretSummed)).To(BeTrue()) 94 | 95 | Expect(SharesAreConsistent(sharesSummed, kmax)).To(BeTrue()) 96 | } 97 | }) 98 | 99 | Specify("adding a constant to shares should result in a consistent sharing of the secrets plus the constant", func() { 100 | indices := RandomIndices(n) 101 | shares := make(Shares, n) 102 | sharesSummed := make(Shares, n) 103 | 104 | for i := 0; i < trials; i++ { 105 | k := RandRange(1, n) 106 | secret := secp256k1.RandomFn() 107 | constant := secp256k1.RandomFn() 108 | secretSummed.Add(&secret, &constant) 109 | 110 | _ = ShareSecret(&shares, indices, secret, k) 111 | 112 | // Construct the summed shares 113 | for i := range sharesSummed { 114 | sharesSummed[i].AddConstant(&shares[i], &constant) 115 | } 116 | 117 | recon := Open(sharesSummed) 118 | Expect(recon.Eq(&secretSummed)).To(BeTrue()) 119 | 120 | Expect(SharesAreConsistent(sharesSummed, k)).To(BeTrue()) 121 | } 122 | }) 123 | }) 124 | 125 | Context("Homomorphic under scaling (3)", func() { 126 | trials := 100 127 | n := 20 128 | 129 | var k int 130 | var secret, scale, secretScaled secp256k1.Fn 131 | 132 | Specify( 133 | "scaled shares should be a consistent sharing of the product of the secret and the scalar", 134 | func() { 135 | indices := RandomIndices(n) 136 | shares := make(Shares, n) 137 | sharesScaled := make(Shares, n) 138 | 139 | for i := 0; i < trials; i++ { 140 | k = RandRange(1, n) 141 | secret = secp256k1.RandomFn() 142 | scale = secp256k1.RandomFn() 143 | secretScaled.Mul(&secret, &scale) 144 | 145 | _ = ShareSecret(&shares, indices, secret, k) 146 | 147 | // Construct the summed shares 148 | for i := range sharesScaled { 149 | sharesScaled[i].Scale(&shares[i], &scale) 150 | } 151 | 152 | recon := Open(sharesScaled) 153 | Expect(recon.Eq(&secretScaled)).To(BeTrue()) 154 | 155 | Expect(SharesAreConsistent(sharesScaled, k)).To(BeTrue()) 156 | } 157 | }, 158 | ) 159 | }) 160 | 161 | // 162 | // Share tests 163 | // 164 | 165 | Context("Shares", func() { 166 | trials := 100 167 | 168 | var bs [ShareSize]byte 169 | var share, share1, share2, shareSum, shareScale Share 170 | 171 | It("should correctly identify shares that have the same index", func() { 172 | var index secp256k1.Fn 173 | var share Share 174 | 175 | for i := 0; i < trials; i++ { 176 | index = secp256k1.RandomFn() 177 | share.Index = index 178 | share.Value = secp256k1.RandomFn() 179 | Expect(share.IndexEq(&index)).To(BeTrue()) 180 | 181 | index = secp256k1.RandomFn() 182 | Expect(share.IndexEq(&index)).To(BeFalse()) 183 | } 184 | }) 185 | 186 | Specify("adding should add the values and leave the index unchanged", func() { 187 | var index, value1, value2, sum secp256k1.Fn 188 | 189 | for i := 0; i < trials; i++ { 190 | index = secp256k1.RandomFn() 191 | value1 = secp256k1.RandomFn() 192 | value2 = secp256k1.RandomFn() 193 | sum.Add(&value1, &value2) 194 | 195 | // The resulting share should have the values added and the 196 | // same index. 197 | share1 = NewShare(index, value1) 198 | share2 = NewShare(index, value2) 199 | shareSum.Add(&share1, &share2) 200 | Expect(shareSum.Value.Eq(&sum)).To(BeTrue()) 201 | Expect(shareSum.Index.Eq(&index)).To(BeTrue()) 202 | 203 | // Adding two shares with different indices should panic. 204 | share1 = NewShare(secp256k1.RandomFn(), value1) 205 | Expect(func() { shareSum.Add(&share1, &share2) }).To(Panic()) 206 | } 207 | }) 208 | 209 | Specify("scaling should multiply the value and leave the index unchanged", func() { 210 | var scale, value, index, prod secp256k1.Fn 211 | 212 | for i := 0; i < trials; i++ { 213 | index = secp256k1.RandomFn() 214 | value = secp256k1.RandomFn() 215 | scale = secp256k1.RandomFn() 216 | prod.Mul(&value, &scale) 217 | 218 | // The resulting share should have the value scaled and the 219 | // same index. 220 | share = NewShare(index, value) 221 | shareScale.Scale(&share, &scale) 222 | Expect(shareScale.Value.Eq(&prod)).To(BeTrue()) 223 | Expect(shareScale.Index.Eq(&index)).To(BeTrue()) 224 | } 225 | }) 226 | 227 | // 228 | // Marshaling 229 | // 230 | 231 | It("should be able to unmarshal into an empty struct", func() { 232 | share1 := NewShare(secp256k1.RandomFn(), secp256k1.RandomFn()) 233 | share2 := Share{} 234 | 235 | _, _, _ = share1.Marshal(bs[:], share1.SizeHint()) 236 | _, m, err := share2.Unmarshal(bs[:], share1.SizeHint()) 237 | Expect(err).ToNot(HaveOccurred()) 238 | Expect(m).To(Equal(0)) 239 | Expect(share1.Eq(&share2)).To(BeTrue()) 240 | }) 241 | }) 242 | 243 | // 244 | // Shares tests 245 | // 246 | 247 | Context("Shares", func() { 248 | const maxN = 20 249 | const maxLen = 4 + maxN*ShareSize 250 | var bs [maxLen]byte 251 | 252 | shares1 := make(Shares, maxN) 253 | shares2 := make(Shares, maxN) 254 | 255 | RandomiseShares := func(shares Shares) { 256 | for i := range shares { 257 | shares[i] = NewShare( 258 | secp256k1.RandomFn(), 259 | secp256k1.RandomFn(), 260 | ) 261 | } 262 | } 263 | 264 | SharesAreEq := func(shares1, shares2 Shares) bool { 265 | if len(shares1) != len(shares2) { 266 | return false 267 | } 268 | for i := range shares1 { 269 | if !shares1[i].Eq(&shares2[i]) { 270 | return false 271 | } 272 | } 273 | return true 274 | } 275 | 276 | It("should be able to unmarshal into an empty struct", func() { 277 | shares1 = shares1[:maxN] 278 | RandomiseShares(shares1) 279 | shares2 = Shares{} 280 | 281 | _, _, _ = shares1.Marshal(bs[:], shares1.SizeHint()) 282 | _, m, err := shares2.Unmarshal(bs[:], shares1.SizeHint()) 283 | Expect(err).ToNot(HaveOccurred()) 284 | Expect(m).To(Equal(0)) 285 | Expect(SharesAreEq(shares1, shares2)).To(BeTrue()) 286 | }) 287 | }) 288 | 289 | // 290 | // Secret sharing tests 291 | // 292 | // We will test the three failure branches of creating shares: 293 | // 294 | // 1. If k is larger than the number of indices, the function will return 295 | // an error. 296 | // 2. If the destination slice is too small to hold all of the shares, the 297 | // function will panic. 298 | // 3. Any of the indices is the zero element. 299 | // 300 | 301 | Context("Sharer", func() { 302 | trials := 100 303 | const n int = 20 304 | 305 | var indices []secp256k1.Fn 306 | 307 | BeforeEach(func() { 308 | indices = RandomIndices(n) 309 | }) 310 | 311 | It("should return an error when k is larger than the number of indices (1)", func() { 312 | maxK := 100 313 | shares := make(Shares, n) 314 | 315 | for i := 0; i < trials; i++ { 316 | k := RandRange(n+1, maxK) 317 | secret := secp256k1.RandomFn() 318 | err := ShareSecret(&shares, indices, secret, k) 319 | 320 | Expect(err).To(HaveOccurred()) 321 | } 322 | }) 323 | 324 | It("should panic if the destination slice capacity is too small (2)", func() { 325 | for i := 0; i < trials; i++ { 326 | k := RandRange(1, n) 327 | secret := secp256k1.RandomFn() 328 | shares := make(Shares, rand.Intn(n)) 329 | Expect(func() { ShareSecret(&shares, indices, secret, k) }).Should(Panic()) 330 | } 331 | }) 332 | 333 | It("should panic if one of the indices is the zero element (3)", func() { 334 | shares := make(Shares, n) 335 | 336 | for i := 0; i < trials; i++ { 337 | indices = RandomIndices(n) 338 | k := RandRange(1, n) 339 | secret := secp256k1.RandomFn() 340 | indices[rand.Intn(len(indices))].Clear() 341 | Expect(func() { ShareSecret(&shares, indices, secret, k) }).Should(Panic()) 342 | } 343 | }) 344 | }) 345 | 346 | // 347 | // Miscellaneous Tests 348 | // 349 | 350 | Context("Constants", func() { 351 | Specify("ShareSize should have correct value", func() { 352 | share := Share{} 353 | Expect(ShareSize).To(Equal(share.SizeHint())) 354 | }) 355 | }) 356 | }) 357 | 358 | func BenchmarkShare(b *testing.B) { 359 | n := 100 360 | k := 33 361 | 362 | indices := RandomIndices(n) 363 | shares := make(Shares, n) 364 | secret := secp256k1.RandomFn() 365 | 366 | b.ResetTimer() 367 | for i := 0; i < b.N; i++ { 368 | _ = ShareSecret(&shares, indices, secret, k) 369 | } 370 | } 371 | 372 | func BenchmarkOpen(b *testing.B) { 373 | n := 100 374 | k := 33 375 | 376 | indices := RandomIndices(n) 377 | shares := make(Shares, n) 378 | secret := secp256k1.RandomFn() 379 | _ = ShareSecret(&shares, indices, secret, k) 380 | Shuffle(shares) 381 | 382 | b.ResetTimer() 383 | for i := 0; i < b.N; i++ { 384 | _ = Open(shares[:k]) 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /shamirutil/misc.go: -------------------------------------------------------------------------------- 1 | package shamirutil 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | // Min returns the maximum of the two given ints. 8 | func Min(a, b int) int { 9 | if a >= b { 10 | return b 11 | } 12 | return a 13 | } 14 | 15 | // Max returns the maximum of the two given ints. 16 | func Max(a, b int) int { 17 | if a <= b { 18 | return b 19 | } 20 | return a 21 | } 22 | 23 | // RandRange returns a random number x such that lower <= x <= upper. 24 | func RandRange(lower, upper int) int { 25 | return rand.Intn(upper+1-lower) + lower 26 | } 27 | -------------------------------------------------------------------------------- /shamirutil/shamir.go: -------------------------------------------------------------------------------- 1 | package shamirutil 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/renproject/secp256k1" 7 | "github.com/renproject/shamir" 8 | ) 9 | 10 | // RandomCommitment constructs and returns a random commitment with the given 11 | // number of curve points. 12 | func RandomCommitment(k int) shamir.Commitment { 13 | c := make(shamir.Commitment, k) 14 | for i := range c { 15 | c[i] = secp256k1.RandomPoint() 16 | } 17 | return c 18 | } 19 | 20 | // RandomIndices initialises and returns a slice of n indices, each of which is 21 | // random. Often it is desired that each index is distinct. This function does 22 | // not gaurantee this, however the chance of two indices being equal is 23 | // negligible for low n. 24 | func RandomIndices(n int) []secp256k1.Fn { 25 | indices := make([]secp256k1.Fn, n) 26 | for i := range indices { 27 | indices[i] = secp256k1.RandomFn() 28 | } 29 | return indices 30 | } 31 | 32 | // SequentialIndices initialises and returns a slice of n indices, where the 33 | // slice index i is equal to i+1 in the field. 34 | func SequentialIndices(n int) []secp256k1.Fn { 35 | indices := make([]secp256k1.Fn, n) 36 | for i := range indices { 37 | indices[i].SetU16(uint16(i) + 1) 38 | } 39 | 40 | return indices 41 | } 42 | 43 | // Shuffle randomises the order of the givens shares in the slice. 44 | func Shuffle(shares shamir.Shares) { 45 | rand.Shuffle(len(shares), func(i, j int) { 46 | shares[i], shares[j] = shares[j], shares[i] 47 | }) 48 | } 49 | 50 | // AddDuplicateIndex picks two random (distinct) indices in the given slice of 51 | // shares and sets the share index of the second to be equal to that of the 52 | // first. 53 | func AddDuplicateIndex(shares shamir.Shares) { 54 | // Pick two distinct array indices. 55 | first, second := rand.Intn(len(shares)), rand.Intn(len(shares)) 56 | for first == second { 57 | second = rand.Intn(len(shares)) 58 | } 59 | 60 | // Set the second share to have the same index as the first. 61 | shares[second].Index = shares[first].Index 62 | } 63 | 64 | // SharesAreConsistent returns true if the given shares are found to be 65 | // consistent. Consistency is defined as all points lying on some polynomial of 66 | // degree less than `k`. 67 | func SharesAreConsistent(shares shamir.Shares, k int) bool { 68 | if len(shares) < k { 69 | return true 70 | } 71 | 72 | secret := shamir.Open(shares[:k]) 73 | for i := 1; i <= len(shares)-k; i++ { 74 | recon := shamir.Open(shares[i : i+k]) 75 | if !recon.Eq(&secret) { 76 | return false 77 | } 78 | } 79 | 80 | return true 81 | } 82 | 83 | // PerturbIndex modifies the given verifiable share to have a random index. 84 | func PerturbIndex(vs *shamir.VerifiableShare) { 85 | vs.Share.Index = secp256k1.RandomFn() 86 | } 87 | 88 | // PerturbValue modifies the given verifiable share to have a random value. 89 | func PerturbValue(vs *shamir.VerifiableShare) { 90 | vs.Share.Value = secp256k1.RandomFn() 91 | } 92 | 93 | // PerturbDecommitment modifies the given verifiable share to have a random 94 | // decommitment value. 95 | func PerturbDecommitment(vs *shamir.VerifiableShare) { 96 | vs.Decommitment = secp256k1.RandomFn() 97 | } 98 | 99 | // VsharesAreConsistent is a wrapper around SharesAreConsistent for the 100 | // VerifiableShares type. 101 | func VsharesAreConsistent( 102 | vshares shamir.VerifiableShares, 103 | k int, 104 | ) bool { 105 | return SharesAreConsistent(vshares.Shares(), k) 106 | } 107 | -------------------------------------------------------------------------------- /vss.go: -------------------------------------------------------------------------------- 1 | package shamir 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | 7 | "github.com/renproject/secp256k1" 8 | "github.com/renproject/surge" 9 | ) 10 | 11 | // VShareSize is the size of a verifiable share in bytes. 12 | const VShareSize = ShareSize + secp256k1.FnSizeMarshalled 13 | 14 | // VerifiableShares is a alias for a slice of VerifiableShare(s). 15 | type VerifiableShares []VerifiableShare 16 | 17 | // SizeHint implements the surge.SizeHinter interface. 18 | func (vshares VerifiableShares) SizeHint() int { return surge.SizeHintU32 + VShareSize*len(vshares) } 19 | 20 | // Marshal implements the surge.Marshaler interface. 21 | func (vshares VerifiableShares) Marshal(buf []byte, rem int) ([]byte, int, error) { 22 | buf, rem, err := surge.MarshalU32(uint32(len(vshares)), buf, rem) 23 | if err != nil { 24 | return buf, rem, err 25 | } 26 | 27 | for i := range vshares { 28 | buf, rem, err = vshares[i].Marshal(buf, rem) 29 | if err != nil { 30 | return buf, rem, err 31 | } 32 | } 33 | 34 | return buf, rem, nil 35 | } 36 | 37 | // Unmarshal implements the surge.Unmarshaler interface. 38 | func (vshares *VerifiableShares) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 39 | var l uint32 40 | buf, rem, err := surge.UnmarshalLen(&l, VShareSize, buf, rem) 41 | if err != nil { 42 | return buf, rem, err 43 | } 44 | 45 | if *vshares == nil { 46 | *vshares = make(VerifiableShares, 0, l) 47 | } 48 | 49 | *vshares = (*vshares)[:0] 50 | for i := uint32(0); i < l; i++ { 51 | *vshares = append(*vshares, VerifiableShare{}) 52 | buf, rem, err = (*vshares)[i].Unmarshal(buf, rem) 53 | if err != nil { 54 | return buf, rem, err 55 | } 56 | } 57 | 58 | return buf, rem, nil 59 | } 60 | 61 | // Shares returns the underlying (unverified) shares. 62 | func (vshares VerifiableShares) Shares() Shares { 63 | shares := make(Shares, len(vshares)) 64 | 65 | for i, vshare := range vshares { 66 | shares[i] = vshare.Share 67 | } 68 | 69 | return shares 70 | } 71 | 72 | // A VerifiableShare is a Share but with additional information that allows it 73 | // to be verified as correct for a given commitment to a sharing. 74 | type VerifiableShare struct { 75 | Share Share 76 | Decommitment secp256k1.Fn 77 | } 78 | 79 | // Generate implements the quick.Generator interface. 80 | func (vs VerifiableShare) Generate(_ *rand.Rand, _ int) reflect.Value { 81 | return reflect.ValueOf( 82 | NewVerifiableShare( 83 | NewShare(secp256k1.RandomFn(), secp256k1.RandomFn()), 84 | secp256k1.RandomFn(), 85 | ), 86 | ) 87 | } 88 | 89 | // NewVerifiableShare constructs a new VerifiableShare from the given Share and 90 | // decommitment value. This function allows the manual construction of a 91 | // VerifiableShare, and should be only used if such fine grained control is 92 | // needed. In general, shares should be constructed by using a VSSharer. 93 | func NewVerifiableShare(share Share, r secp256k1.Fn) VerifiableShare { 94 | return VerifiableShare{share, r} 95 | } 96 | 97 | // Eq returns true if the two verifiable shares are equal, and false otherwise. 98 | func (vs *VerifiableShare) Eq(other *VerifiableShare) bool { 99 | return vs.Share.Eq(&other.Share) && vs.Decommitment.Eq(&other.Decommitment) 100 | } 101 | 102 | // SizeHint implements the surge.SizeHinter interface. 103 | func (vs VerifiableShare) SizeHint() int { return vs.Share.SizeHint() + vs.Decommitment.SizeHint() } 104 | 105 | // Marshal implements the surge.Marshaler interface. 106 | func (vs VerifiableShare) Marshal(buf []byte, rem int) ([]byte, int, error) { 107 | buf, rem, err := vs.Share.Marshal(buf, rem) 108 | if err != nil { 109 | return buf, rem, err 110 | } 111 | 112 | buf, rem, err = vs.Decommitment.Marshal(buf, rem) 113 | return buf, rem, err 114 | } 115 | 116 | // Unmarshal implements the surge.Unmarshaler interface. 117 | func (vs *VerifiableShare) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 118 | buf, rem, err := vs.Share.Unmarshal(buf, rem) 119 | if err != nil { 120 | return buf, rem, err 121 | } 122 | 123 | return vs.Decommitment.Unmarshal(buf, rem) 124 | } 125 | 126 | // Add computes the addition of the two input shares and stores the result in 127 | // the caller. This is defined as adding the respective normal (unverifiable) 128 | // shares and adding the respective decommitment values. In general, the 129 | // resulting share will be a share with secret value equal to the sum of the 130 | // two secrets corresponding to the (respective sharings of the) input shares. 131 | func (vs *VerifiableShare) Add(a, b *VerifiableShare) { 132 | vs.Share.Add(&a.Share, &b.Share) 133 | vs.Decommitment.Add(&a.Decommitment, &b.Decommitment) 134 | } 135 | 136 | // AddConstant computes the addition of the input share and the constant and 137 | // stores the result in the caller. This is defined as adding the constant to 138 | // the normal (unverifiable) share and leaving the decommitment value 139 | // unchanged. In general, the resulting share will be a share with secret value 140 | // equal to the sum of the secret corresponding to the input share and the 141 | // constant. 142 | func (vs *VerifiableShare) AddConstant(other *VerifiableShare, c *secp256k1.Fn) { 143 | vs.Decommitment = other.Decommitment 144 | vs.Share.AddConstant(&other.Share, c) 145 | } 146 | 147 | // Scale computes the scaling of the input share by given scale factor and 148 | // stores the result in the caller. This is defined as scaling the normal 149 | // (unverifiable) share by the scaling factor and multiplying the decommitment 150 | // value also by the scaling factor. In general, the resulting share will be a 151 | // share with secret value equal to the scale factor multiplied by the secret 152 | // corresponding to the (sharing of the) input share. 153 | func (vs *VerifiableShare) Scale(other *VerifiableShare, scale *secp256k1.Fn) { 154 | vs.Share.Scale(&other.Share, scale) 155 | vs.Decommitment.Mul(&other.Decommitment, scale) 156 | } 157 | 158 | // A Commitment is used to verify that a sharing has been performed correctly. 159 | type Commitment []secp256k1.Point 160 | 161 | // Generate implements the quick.Generator interface. 162 | func (c Commitment) Generate(rand *rand.Rand, size int) reflect.Value { 163 | com := make(Commitment, rand.Intn(size)) 164 | for i := range com { 165 | com[i] = secp256k1.RandomPoint() 166 | } 167 | return reflect.ValueOf(com) 168 | } 169 | 170 | // Eq returns true if the two commitments are equal (each curve point is 171 | // equal), and false otherwise. 172 | func (c Commitment) Eq(other Commitment) bool { 173 | if len(c) != len(other) { 174 | return false 175 | } 176 | 177 | for i := range c { 178 | if !c[i].Eq(&other[i]) { 179 | return false 180 | } 181 | } 182 | 183 | return true 184 | } 185 | 186 | // Append a point to the commitment. 187 | func (c *Commitment) Append(p secp256k1.Point) { 188 | *c = append(*c, p) 189 | } 190 | 191 | // Len returns the number of curve points in the commitment. This is equal to 192 | // the reconstruction threshold of the associated verifiable sharing. 193 | func (c Commitment) Len() int { 194 | return len(c) 195 | } 196 | 197 | // Set the calling commitment to be equal to the given commitment. 198 | func (c *Commitment) Set(other Commitment) { 199 | if len(*c) < len(other) { 200 | *c = NewCommitmentWithCapacity(len(other)) 201 | } 202 | 203 | *c = (*c)[:len(other)] 204 | for i := range *c { 205 | (*c)[i] = other[i] 206 | } 207 | } 208 | 209 | // SizeHint implements the surge.SizeHinter interface. 210 | func (c Commitment) SizeHint() int { 211 | return surge.SizeHintU32 + secp256k1.PointSizeMarshalled*len(c) 212 | } 213 | 214 | // Marshal implements the surge.Marshaler interface. 215 | func (c Commitment) Marshal(buf []byte, rem int) ([]byte, int, error) { 216 | buf, rem, err := surge.MarshalU32(uint32(len(c)), buf, rem) 217 | if err != nil { 218 | return buf, rem, err 219 | } 220 | 221 | for i := range c { 222 | buf, rem, err = c[i].Marshal(buf, rem) 223 | if err != nil { 224 | return buf, rem, err 225 | } 226 | } 227 | 228 | return buf, rem, nil 229 | } 230 | 231 | // Unmarshal implements the surge.Unmarshaler interface. 232 | func (c *Commitment) Unmarshal(buf []byte, rem int) ([]byte, int, error) { 233 | var l uint32 234 | buf, rem, err := surge.UnmarshalLen(&l, secp256k1.PointSize, buf, rem) 235 | if err != nil { 236 | return buf, rem, err 237 | } 238 | 239 | if *c == nil { 240 | *c = make([]secp256k1.Point, 0, l) 241 | } 242 | 243 | *c = (*c)[:0] 244 | for i := uint32(0); i < l; i++ { 245 | *c = append(*c, secp256k1.Point{}) 246 | buf, rem, err = (*c)[i].Unmarshal(buf, rem) 247 | if err != nil { 248 | return buf, rem, err 249 | } 250 | } 251 | return buf, rem, nil 252 | } 253 | 254 | // NewCommitmentWithCapacity creates a new Commitment with the given capacity. 255 | // This capacity represents the maximum reconstruction threshold, k, that this 256 | // commitment can be used for. 257 | func NewCommitmentWithCapacity(k int) Commitment { 258 | return make(Commitment, 0, k) 259 | } 260 | 261 | // Add takes two input commitments and stores in the caller the commitment that 262 | // represents the addition of these two commitments. That is, the new 263 | // commitment can be used to verify the correctness of the sharing defined by 264 | // adding the two corresponding sharings for the input commitments. For 265 | // example, if `a_i` is a valid share for the commitment `a`, and `b_i` is a 266 | // valid share for the commitment `b`, then `a_i + b_i` will be a valid share 267 | // for the newly constructed commitment. 268 | // 269 | // Panics: If the destination commitment does not have capacity at least as big 270 | // as the greater of the capacities of the two inputs, then this function will 271 | // panic. 272 | func (c *Commitment) Add(a, b Commitment) { 273 | var smaller, larger []secp256k1.Point 274 | if len(a) > len(b) { 275 | smaller, larger = b, a 276 | } else { 277 | smaller, larger = a, b 278 | } 279 | 280 | *c = (*c)[:len(larger)] 281 | for i := range smaller { 282 | (*c)[i].Add(&smaller[i], &larger[i]) 283 | } 284 | copy((*c)[len(smaller):], larger[len(smaller):]) 285 | } 286 | 287 | // Add takes an input commitment and a constant and stores in the caller the 288 | // commitment that represents the addition of this commitment and the constant. 289 | // That is, the new commitment can be used to verify the correctness of the 290 | // sharing defined by adding the constant to the corresponding sharing for the 291 | // input commitment. For example, if `a_i` is a valid share for the commitment 292 | // `a`, and `c` is a constant, then `a_i.AddConstant(c)` will be a valid share 293 | // for the newly constructed commitment. 294 | func (c *Commitment) AddConstant(other Commitment, constant *secp256k1.Fn) { 295 | c.Set(other) 296 | point := secp256k1.Point{} 297 | point.BaseExp(constant) 298 | (*c)[0].Add(&(*c)[0], &point) 299 | } 300 | 301 | // Scale takes an input commitment and stores in the caller the commitment that 302 | // represents the scaled input commitment. That is, the new commitment can be 303 | // used to verify the correctness of the sharing defined by scaling the 304 | // original sharing. For example, if `a_i` is a valid share for the commitment 305 | // `other`, then `scale * a_i` will be a valid sharing for the newly 306 | // constructed commitment. 307 | // 308 | // Panics: If the destination commitment does not have capacity at least as big 309 | // as the input commitment, then this function will panic. 310 | func (c *Commitment) Scale(other Commitment, scale *secp256k1.Fn) { 311 | *c = (*c)[:len(other)] 312 | for i := range *c { 313 | (*c)[i].Scale(&other[i], scale) 314 | } 315 | } 316 | 317 | // Evaluates the sharing polynomial at the given index "in the exponent". 318 | func (c *Commitment) evaluate(eval *secp256k1.Point, index *secp256k1.Fn) { 319 | *eval = (*c)[len(*c)-1] 320 | for i := len(*c) - 2; i >= 0; i-- { 321 | eval.Scale(eval, index) 322 | eval.Add(eval, &(*c)[i]) 323 | } 324 | } 325 | 326 | // IsValid returns true when the given verifiable share is valid with regard to 327 | // the given commitment, and false otherwise. 328 | func IsValid(h secp256k1.Point, c *Commitment, vshare *VerifiableShare) bool { 329 | var gPow, hPow, eval secp256k1.Point 330 | gPow.BaseExp(&vshare.Share.Value) 331 | hPow.Scale(&h, &vshare.Decommitment) 332 | gPow.Add(&gPow, &hPow) 333 | 334 | c.evaluate(&eval, &vshare.Share.Index) 335 | return gPow.Eq(&eval) 336 | } 337 | 338 | // VShareSecret creates verifiable Shamir shares for the given secret at the 339 | // given threshold, and stores the shares and the commitment in the given 340 | // destinations. In the returned Shares, there will be one share for each index 341 | // in the indices that were used to construct the Sharer. 342 | // 343 | // Panics: This function will panic if the destination shares slice has a 344 | // capacity less than n (the number of indices), or if the destination 345 | // commitment has a capacity less than k. 346 | func VShareSecret( 347 | vshares *VerifiableShares, 348 | c *Commitment, 349 | indices []secp256k1.Fn, 350 | h secp256k1.Point, 351 | secret secp256k1.Fn, 352 | k int, 353 | ) error { 354 | n := len(indices) 355 | shares := make(Shares, n) 356 | coeffs := make([]secp256k1.Fn, k) 357 | err := ShareAndGetCoeffs(&shares, coeffs, indices, secret, k) 358 | if err != nil { 359 | return err 360 | } 361 | 362 | // At this point, the sharer should still have the randomly picked 363 | // coefficients in its cache, which we need to use for the commitment. 364 | *c = (*c)[:k] 365 | for i, coeff := range coeffs { 366 | (*c)[i].BaseExp(&coeff) 367 | } 368 | 369 | setRandomCoeffs(coeffs, secp256k1.RandomFn(), k) 370 | for i, ind := range indices { 371 | (*vshares)[i].Share = shares[i] 372 | polyEval(&(*vshares)[i].Decommitment, &ind, coeffs) 373 | } 374 | 375 | // Finish the computation of the commitments 376 | var hPow secp256k1.Point 377 | for i, coeff := range coeffs { 378 | hPow.Scale(&h, &coeff) 379 | (*c)[i].Add(&(*c)[i], &hPow) 380 | } 381 | 382 | return nil 383 | } 384 | -------------------------------------------------------------------------------- /vss_test.go: -------------------------------------------------------------------------------- 1 | package shamir_test 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | 8 | "github.com/renproject/secp256k1" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | . "github.com/renproject/shamir" 13 | . "github.com/renproject/shamir/shamirutil" 14 | ) 15 | 16 | // The key properties of verifiable secret sharing is that it is the same as 17 | // normal secret sharing, except that additional auxiliary information is 18 | // included within and without the shares that allows other parties to verify 19 | // that any shares they receive are correct. Thus to reduce overlap with 20 | // testing for standard Shamir shares, we aim to test the properties that are 21 | // unique to verifiable shares. These are as follows. 22 | // 23 | // 1. Correctness: The shares and commitments produced by the relevant 24 | // function calls should be correct. That is, all thusly created shares should 25 | // constitute a consistent sharing of some secret, and should be found to be 26 | // valid when checking their validity using the auxiliary information. 27 | // 28 | // 2. Soundness: Any shares that are altered after that have been correctly 29 | // constructed by the VSS scheme should be detectable. That is, when checking 30 | // the validity of such a share with the produced auxiliary information, the 31 | // check should fail. 32 | // 33 | // 3. Homomorphic under addition: Pedersen VSS is homomorphic under addition, 34 | // which means that if we have two verifiable shares and their respective 35 | // commitments from two different sharings of respective secrets, we can "add" 36 | // the shares and the commitments such that we end up with a new share and 37 | // commitment. Further, this new share and commitment will form part of a 38 | // valid verifiable sharing of the sum of the two original secrets. 39 | // 40 | // 4. Homomorphic under addition of a constant: Pedersen VSS is also 41 | // homomorphic under addition by some public constant value. We require a 42 | // property analogous to point 3 in this case. 43 | // 44 | // 5. Homomorphic under scaling: Pedersen VSS is also homomorphic under 45 | // scaling by some public constant value. We require a property analogous to 46 | // point 3 in this case. 47 | // 48 | var _ = Describe("Verifiable secret sharing", func() { 49 | rand.Seed(time.Now().UnixNano()) 50 | 51 | // Pedersen commitment parameter. This curve point needs to be a generator 52 | // of the elliptic curve group. Since the group has prime order, any curve 53 | // point is a generator (expect for the identity), and so we may just one 54 | // at random. Note that in practice it is crucial that no one knows 55 | // log_g(h), that is, no one should know a number x such that h = g^x. For 56 | // testing this obivously does not matter. 57 | h := secp256k1.RandomPoint() 58 | 59 | Context("Correctness (1)", func() { 60 | trials := 20 61 | n := 20 62 | 63 | var k int 64 | var secret secp256k1.Fn 65 | 66 | indices := RandomIndices(n) 67 | vshares := make(VerifiableShares, n) 68 | c := NewCommitmentWithCapacity(n) 69 | 70 | Specify("all shares constructed from the VSS scheme should be valid", func() { 71 | for i := 0; i < trials; i++ { 72 | // Create a random sharing. 73 | k = RandRange(1, n) 74 | secret = secp256k1.RandomFn() 75 | err := VShareSecret(&vshares, &c, indices, h, secret, k) 76 | Expect(err).ToNot(HaveOccurred()) 77 | 78 | // Check that all shares are valid. 79 | for _, share := range vshares { 80 | Expect(IsValid(h, &c, &share)).To(BeTrue()) 81 | } 82 | } 83 | }) 84 | }) 85 | 86 | // Tests for the soundness property (2). We want to check that any shares 87 | // that get altered are detected by the checker. There are three ways in 88 | // which a share can be altered: 89 | // 90 | // 1. The index of the share could be changed. 91 | // 2. The value of the share could be changed. 92 | // 3. The decommitment value of the verifiable share could be changed. 93 | Context("Soundness (2)", func() { 94 | trials := 20 95 | n := 20 96 | 97 | var k, badInd int 98 | var indices []secp256k1.Fn 99 | var vshares VerifiableShares 100 | var c Commitment 101 | var secret secp256k1.Fn 102 | 103 | BeforeEach(func() { 104 | indices = RandomIndices(n) 105 | vshares = make(VerifiableShares, n) 106 | c = NewCommitmentWithCapacity(n) 107 | }) 108 | 109 | ShareAndCheckWithPerturbed := func(kLower int, perturbShare func(vs *VerifiableShare)) { 110 | for i := 0; i < trials; i++ { 111 | k = RandRange(kLower, n) 112 | secret = secp256k1.RandomFn() 113 | err := VShareSecret(&vshares, &c, indices, h, secret, k) 114 | Expect(err).ToNot(HaveOccurred()) 115 | 116 | // Change one of the shares to be invalid 117 | badInd = rand.Intn(n) 118 | perturbShare(&vshares[badInd]) 119 | 120 | for i, share := range vshares { 121 | Expect(IsValid(h, &c, &share)).To(Equal(i != badInd)) 122 | } 123 | } 124 | } 125 | 126 | Specify("a share with a modified index should be invalid (1)", func() { 127 | // We need to ensure that k is at least 2, otherwise every point on 128 | // the sharing polynomial is the same and changing the index won't 129 | // actually make the share invalid. 130 | ShareAndCheckWithPerturbed(2, PerturbIndex) 131 | }) 132 | 133 | Specify("a share with a modified value should be invalid (2)", func() { 134 | ShareAndCheckWithPerturbed(1, PerturbValue) 135 | }) 136 | 137 | Specify("a share with a modified decommitment should be invalid (3)", func() { 138 | ShareAndCheckWithPerturbed(1, PerturbDecommitment) 139 | }) 140 | }) 141 | 142 | // Tests for the Homomorphic addition property (3). This property states 143 | // that if we have two sharings and then add them together (including the 144 | // auxiliary information), we should get a new sharing that is valid and 145 | // corresponds to the sum of the original two secrets. Specifically, we 146 | // want the following to hold after adding two verifiable sharings 147 | // together: 148 | // 149 | // 1. Each summed share should be valid when checked against the new 150 | // "summed" auxiliary information. 151 | // 2. If one of the newly created shares is altered in any way, this share 152 | // should fail the validity check of the new auxiliary information. 153 | // 3. The summed shares should form a consistent sharing of the secret 154 | // that is defined as the sum of the two original secrets. 155 | Context("Homomorphic addition (3)", func() { 156 | trials := 20 157 | n := 20 158 | 159 | var k1, k2, kmax int 160 | var indices []secp256k1.Fn 161 | var vshares1, vshares2, vsharesSummed VerifiableShares 162 | var c1, c2, cSummed Commitment 163 | var secret1, secret2, secretSummed secp256k1.Fn 164 | 165 | BeforeEach(func() { 166 | indices = RandomIndices(n) 167 | vshares1 = make(VerifiableShares, n) 168 | vshares2 = make(VerifiableShares, n) 169 | vsharesSummed = make(VerifiableShares, n) 170 | c1 = NewCommitmentWithCapacity(n) 171 | c2 = NewCommitmentWithCapacity(n) 172 | cSummed = NewCommitmentWithCapacity(n) 173 | }) 174 | 175 | CreateShares := func(kLower int) { 176 | k1 = RandRange(kLower, n) 177 | k2 = RandRange(kLower, n) 178 | kmax = Max(k1, k2) 179 | secret1 = secp256k1.RandomFn() 180 | secret2 = secp256k1.RandomFn() 181 | secretSummed.Add(&secret1, &secret2) 182 | _ = VShareSecret(&vshares1, &c1, indices, h, secret1, k1) 183 | _ = VShareSecret(&vshares2, &c2, indices, h, secret2, k2) 184 | 185 | // Create the shares for the sum 186 | cSummed.Add(c1, c2) 187 | for i := range vsharesSummed { 188 | vsharesSummed[i].Add(&vshares1[i], &vshares2[i]) 189 | } 190 | } 191 | 192 | PerturbAndCheck := func(perturb func(vs *VerifiableShare)) { 193 | badInd := rand.Intn(n) 194 | perturb(&vsharesSummed[badInd]) 195 | 196 | // The shares should be valid 197 | for i, share := range vsharesSummed { 198 | Expect(IsValid(h, &cSummed, &share)).To(Equal(i != badInd)) 199 | } 200 | } 201 | 202 | Specify("the summed shares should be valid (1)", func() { 203 | for i := 0; i < trials; i++ { 204 | CreateShares(1) 205 | 206 | // The shares should be valid 207 | for _, share := range vsharesSummed { 208 | Expect(IsValid(h, &cSummed, &share)).To(BeTrue()) 209 | } 210 | } 211 | }) 212 | 213 | // The parts of a share that can be maliciously altered are the: 214 | // 1. Index 215 | // 2. Value 216 | // 3. Decommitment 217 | Specify("a share with an altered index should be detected (2.1)", func() { 218 | for i := 0; i < trials; i++ { 219 | // We need to ensure that k is at least 2, otherwise every 220 | // point on the sharing polynomial is the same and changing the 221 | // index won't actually make the share invalid. 222 | CreateShares(2) 223 | PerturbAndCheck(PerturbIndex) 224 | } 225 | }) 226 | 227 | Specify("a share with an altered value should be detected (2.2)", func() { 228 | for i := 0; i < trials; i++ { 229 | CreateShares(1) 230 | PerturbAndCheck(PerturbValue) 231 | } 232 | }) 233 | 234 | Specify("a share with an altered decommitment should be detected (2.3)", func() { 235 | for i := 0; i < trials; i++ { 236 | CreateShares(1) 237 | PerturbAndCheck(PerturbDecommitment) 238 | } 239 | }) 240 | 241 | Specify("the resulting secret should be the sum of the original secrets (3)", func() { 242 | for i := 0; i < trials; i++ { 243 | CreateShares(1) 244 | 245 | sharesSummed := vsharesSummed.Shares() 246 | recon := Open(sharesSummed) 247 | Expect(recon.Eq(&secretSummed)).To(BeTrue()) 248 | 249 | Expect(VsharesAreConsistent(vsharesSummed, kmax)).To(BeTrue()) 250 | } 251 | }) 252 | }) 253 | 254 | // Tests for the Homomorphic addition of a constant property (4). This 255 | // property states that if we have a sharing and then add a public constant 256 | // (also adding the constant to the auxiliary information), we should get a 257 | // new sharing that is valid and corresponds to the sum of the original 258 | // secret and the constant. Specifically, we want the following to hold 259 | // after adding a constant to a sharing: 260 | // 261 | // 1. Each resulting share should be valid when checked against the new 262 | // resulting auxiliary information. 263 | // 2. If one of the newly created shares is altered in any way, this share 264 | // should fail the validity check of the new auxiliary information. 265 | // 3. The resulting shares should form a consistent sharing of the secret 266 | // that is defined as the sum of the original secret and the constant. 267 | Context("Homomorphic addition of constant (4)", func() { 268 | trials := 20 269 | n := 20 270 | 271 | var k int 272 | var indices []secp256k1.Fn 273 | var vshares, vsharesSummed VerifiableShares 274 | var c, cSummed Commitment 275 | var secret, constant, secretSummed secp256k1.Fn 276 | 277 | BeforeEach(func() { 278 | indices = RandomIndices(n) 279 | vshares = make(VerifiableShares, n) 280 | vsharesSummed = make(VerifiableShares, n) 281 | c = NewCommitmentWithCapacity(n) 282 | cSummed = NewCommitmentWithCapacity(n) 283 | }) 284 | 285 | CreateShares := func(kLower int) { 286 | k = RandRange(kLower, n) 287 | secret = secp256k1.RandomFn() 288 | constant = secp256k1.RandomFn() 289 | secretSummed.Add(&secret, &constant) 290 | _ = VShareSecret(&vshares, &c, indices, h, secret, k) 291 | 292 | // Create the shares for the sum 293 | cSummed.AddConstant(c, &constant) 294 | for i := range vsharesSummed { 295 | vsharesSummed[i].AddConstant(&vshares[i], &constant) 296 | } 297 | } 298 | 299 | PerturbAndCheck := func(perturb func(vs *VerifiableShare)) { 300 | badInd := rand.Intn(n) 301 | perturb(&vsharesSummed[badInd]) 302 | 303 | // The shares should be valid 304 | for i, share := range vsharesSummed { 305 | Expect(IsValid(h, &cSummed, &share)).To(Equal(i != badInd)) 306 | } 307 | } 308 | 309 | Specify("the summed shares should be valid (1)", func() { 310 | for i := 0; i < trials; i++ { 311 | CreateShares(1) 312 | 313 | // The shares should be valid 314 | for _, share := range vsharesSummed { 315 | Expect(IsValid(h, &cSummed, &share)).To(BeTrue()) 316 | } 317 | } 318 | }) 319 | 320 | // The parts of a share that can be maliciously altered are the: 321 | // 1. Index 322 | // 2. Value 323 | // 3. Decommitment 324 | Specify("a share with an altered index should be detected (2.1)", func() { 325 | for i := 0; i < trials; i++ { 326 | // We need to ensure that k is at least 2, otherwise every 327 | // point on the sharing polynomial is the same and changing the 328 | // index won't actually make the share invalid. 329 | CreateShares(2) 330 | PerturbAndCheck(PerturbIndex) 331 | } 332 | }) 333 | 334 | Specify("a share with an altered value should be detected (2.2)", func() { 335 | for i := 0; i < trials; i++ { 336 | CreateShares(1) 337 | PerturbAndCheck(PerturbValue) 338 | } 339 | }) 340 | 341 | Specify("a share with an altered decommitment should be detected (2.3)", func() { 342 | for i := 0; i < trials; i++ { 343 | CreateShares(1) 344 | PerturbAndCheck(PerturbDecommitment) 345 | } 346 | }) 347 | 348 | Specify("the resulting secret should be the sum of the original secret and the constant (3)", func() { 349 | for i := 0; i < trials; i++ { 350 | CreateShares(1) 351 | 352 | sharesSummed := vsharesSummed.Shares() 353 | recon := Open(sharesSummed) 354 | Expect(recon.Eq(&secretSummed)).To(BeTrue()) 355 | 356 | Expect(VsharesAreConsistent(vsharesSummed, k)).To(BeTrue()) 357 | } 358 | }) 359 | }) 360 | 361 | // Tests for the Homomorphic scaling property (4). This property states 362 | // that if we have a sharing and then scale it by some scalar (including 363 | // the auxiliary information), we should get a new sharing that is valid 364 | // and corresponds to the product of the original two secret and the 365 | // scalar. Specifically, we want the following to hold after scaling a 366 | // verifiable sharing: 367 | // 368 | // 1. Each scaled share should be valid when checked against the new 369 | // "scaled" auxiliary information. 370 | // 2. If one of the newly created shares is altered in any way, this share 371 | // should fail the validity check of the new auxiliary information. 372 | // 3. The scaled shares should form a consistent sharing of the secret 373 | // that is defined as the product of the original secret and the scalar. 374 | Context("Homomorphic scaling (5)", func() { 375 | trials := 20 376 | n := 20 377 | 378 | var k int 379 | var indices []secp256k1.Fn 380 | var vshares, vsharesScaled VerifiableShares 381 | var c, cScaled Commitment 382 | var secret, scale, secretScaled secp256k1.Fn 383 | 384 | BeforeEach(func() { 385 | indices = RandomIndices(n) 386 | vshares = make(VerifiableShares, n) 387 | vsharesScaled = make(VerifiableShares, n) 388 | c = NewCommitmentWithCapacity(n) 389 | cScaled = NewCommitmentWithCapacity(n) 390 | }) 391 | 392 | CreateShares := func(kLower int) { 393 | k = RandRange(kLower, n) 394 | secret = secp256k1.RandomFn() 395 | scale = secp256k1.RandomFn() 396 | secretScaled.Mul(&secret, &scale) 397 | _ = VShareSecret(&vshares, &c, indices, h, secret, k) 398 | 399 | // Create the scaled shares 400 | cScaled.Scale(c, &scale) 401 | for i := range vsharesScaled { 402 | vsharesScaled[i].Scale(&vshares[i], &scale) 403 | } 404 | } 405 | 406 | PerturbAndCheck := func(perturb func(vs *VerifiableShare)) { 407 | badInd := rand.Intn(n) 408 | perturb(&vsharesScaled[badInd]) 409 | 410 | // The shares should be valid 411 | for i, share := range vsharesScaled { 412 | Expect(IsValid(h, &cScaled, &share)).To(Equal(i != badInd)) 413 | } 414 | } 415 | 416 | Specify("the scaled shares should be valid (1)", func() { 417 | for i := 0; i < trials; i++ { 418 | CreateShares(1) 419 | 420 | // The shares should be valid 421 | for _, share := range vsharesScaled { 422 | Expect(IsValid(h, &cScaled, &share)).To(BeTrue()) 423 | } 424 | } 425 | }) 426 | 427 | // The parts of a share that can be maliciously altered are the: 428 | // 1. Index 429 | // 2. Value 430 | // 3. Decommitment 431 | Specify("a share with an altered index should be detected (2.1)", func() { 432 | for i := 0; i < trials; i++ { 433 | // We need to ensure that k is at least 2, otherwise every 434 | // point on the sharing polynomial is the same and changing the 435 | // index won't actually make the share invalid. 436 | CreateShares(2) 437 | PerturbAndCheck(PerturbIndex) 438 | } 439 | }) 440 | 441 | Specify("a share with an altered value should be detected (2.2)", func() { 442 | for i := 0; i < trials; i++ { 443 | CreateShares(1) 444 | PerturbAndCheck(PerturbValue) 445 | } 446 | }) 447 | 448 | Specify("a share with an altered decommitment should be detected (2.3)", func() { 449 | for i := 0; i < trials; i++ { 450 | CreateShares(1) 451 | PerturbAndCheck(PerturbDecommitment) 452 | } 453 | }) 454 | 455 | Specify("the resulting secret should be the product of the original secret and the scale (3)", func() { 456 | for i := 0; i < trials; i++ { 457 | CreateShares(1) 458 | 459 | sharesScaled := vsharesScaled.Shares() 460 | recon := Open(sharesScaled) 461 | Expect(recon.Eq(&secretScaled)).To(BeTrue()) 462 | 463 | Expect(VsharesAreConsistent(vsharesScaled, k)).To(BeTrue()) 464 | } 465 | }) 466 | }) 467 | 468 | // 469 | // Miscellaneous tests 470 | // 471 | 472 | Specify("trying to share when k is larger than n should fail", func() { 473 | n := 20 474 | 475 | indices := RandomIndices(n) 476 | 477 | err := VShareSecret(nil, nil, indices, h, secp256k1.Fn{}, n+1) 478 | Expect(err).To(HaveOccurred()) 479 | }) 480 | 481 | Context("Commitments", func() { 482 | trials := 100 483 | maxK := 10 484 | 485 | Specify("should be unequal when they have different lengths", func() { 486 | for i := 0; i < trials; i++ { 487 | k1 := rand.Intn(maxK) + 1 488 | k2 := k1 489 | for k2 == k1 { 490 | k2 = rand.Intn(maxK) + 1 491 | } 492 | com1 := RandomCommitment(k1) 493 | com2 := RandomCommitment(k2) 494 | 495 | Expect(com1.Eq(com2)).To(BeFalse()) 496 | } 497 | }) 498 | 499 | Specify("should be unequal when they have different curve points", func() { 500 | for i := 0; i < trials; i++ { 501 | k := rand.Intn(maxK) + 1 502 | com1 := RandomCommitment(k) 503 | com2 := RandomCommitment(k) 504 | 505 | Expect(com1.Eq(com2)).To(BeFalse()) 506 | } 507 | }) 508 | 509 | Specify("setting a commitment should make it equal to the argument", func() { 510 | var com2 Commitment 511 | for i := 0; i < trials; i++ { 512 | k := rand.Intn(maxK) + 1 513 | com1 := RandomCommitment(k) 514 | com2.Set(com1) 515 | 516 | Expect(com1.Eq(com2)).To(BeTrue()) 517 | } 518 | }) 519 | 520 | Specify("accessing and appending elements should work correctly", func() { 521 | points := make([]secp256k1.Point, maxK) 522 | 523 | for i := 0; i < trials; i++ { 524 | k := rand.Intn(maxK) + 1 525 | com := NewCommitmentWithCapacity(k) 526 | for j := 0; j < k; j++ { 527 | points[j] = secp256k1.RandomPoint() 528 | com.Append(points[j]) 529 | } 530 | 531 | for j := 0; j < k; j++ { 532 | p := com[j] 533 | Expect(p.Eq(&points[j])).To(BeTrue()) 534 | } 535 | } 536 | }) 537 | 538 | It("should return the correct number of curve points", func() { 539 | for i := 0; i < trials; i++ { 540 | k := rand.Intn(maxK) + 1 541 | com := RandomCommitment(k) 542 | Expect(com.Len()).To(Equal(k)) 543 | } 544 | }) 545 | }) 546 | 547 | Context("Verifiable shares", func() { 548 | It("should be able to unmarshal into an empty struct", func() { 549 | var bs [VShareSize]byte 550 | share1 := NewVerifiableShare( 551 | NewShare(secp256k1.RandomFn(), secp256k1.RandomFn()), 552 | secp256k1.RandomFn(), 553 | ) 554 | share2 := VerifiableShare{} 555 | 556 | _, _, _ = share1.Marshal(bs[:], share1.SizeHint()) 557 | _, m, err := share2.Unmarshal(bs[:], share1.SizeHint()) 558 | Expect(err).ToNot(HaveOccurred()) 559 | Expect(m).To(Equal(0)) 560 | Expect(share1.Eq(&share2)).To(BeTrue()) 561 | }) 562 | }) 563 | 564 | // 565 | // VerifiableShares tests 566 | // 567 | 568 | Context("VerifiableShares", func() { 569 | const maxN = 20 570 | const maxLen = 4 + maxN*VShareSize 571 | var bs [maxLen]byte 572 | 573 | shares1 := make(VerifiableShares, maxN) 574 | shares2 := make(VerifiableShares, maxN) 575 | 576 | RandomiseVerifiableShares := func(shares VerifiableShares) { 577 | for i := range shares { 578 | shares[i] = NewVerifiableShare( 579 | NewShare( 580 | secp256k1.RandomFn(), 581 | secp256k1.RandomFn(), 582 | ), 583 | secp256k1.RandomFn(), 584 | ) 585 | } 586 | } 587 | 588 | VerifiableSharesAreEq := func(shares1, shares2 VerifiableShares) bool { 589 | if len(shares1) != len(shares2) { 590 | return false 591 | } 592 | for i := range shares1 { 593 | if !shares1[i].Eq(&shares2[i]) { 594 | return false 595 | } 596 | } 597 | return true 598 | } 599 | 600 | It("should be able to unmarshal into an empty struct", func() { 601 | shares1 = shares1[:maxN] 602 | RandomiseVerifiableShares(shares1) 603 | shares2 = VerifiableShares{} 604 | 605 | _, _, _ = shares1.Marshal(bs[:], shares1.SizeHint()) 606 | _, m, err := shares2.Unmarshal(bs[:], shares1.SizeHint()) 607 | Expect(err).ToNot(HaveOccurred()) 608 | Expect(m).To(Equal(0)) 609 | Expect(VerifiableSharesAreEq(shares1, shares2)).To(BeTrue()) 610 | }) 611 | }) 612 | 613 | Context("Constants", func() { 614 | Specify("VShareSize should have the correct value", func() { 615 | vshare := VerifiableShare{} 616 | Expect(VShareSize).To(Equal(vshare.SizeHint())) 617 | }) 618 | }) 619 | }) 620 | 621 | func BenchmarkVSShare(b *testing.B) { 622 | n := 100 623 | k := 33 624 | h := secp256k1.RandomPoint() 625 | 626 | indices := RandomIndices(n) 627 | vshares := make(VerifiableShares, n) 628 | c := NewCommitmentWithCapacity(n) 629 | secret := secp256k1.RandomFn() 630 | 631 | b.ResetTimer() 632 | for i := 0; i < b.N; i++ { 633 | _ = VShareSecret(&vshares, &c, indices, h, secret, k) 634 | } 635 | } 636 | 637 | func BenchmarkVSSVerify(b *testing.B) { 638 | n := 100 639 | k := 33 640 | h := secp256k1.RandomPoint() 641 | 642 | indices := RandomIndices(n) 643 | vshares := make(VerifiableShares, n) 644 | c := NewCommitmentWithCapacity(n) 645 | secret := secp256k1.RandomFn() 646 | _ = VShareSecret(&vshares, &c, indices, h, secret, k) 647 | ind := rand.Intn(n) 648 | share := vshares[ind] 649 | 650 | b.ResetTimer() 651 | for i := 0; i < b.N; i++ { 652 | IsValid(h, &c, &share) 653 | } 654 | } 655 | --------------------------------------------------------------------------------