Skip to content

Commit

Permalink
new changes in the API to better handling of messages, default prime …
Browse files Browse the repository at this point in the history
…number updated to support bigger messages in lower number of resulting shares
  • Loading branch information
lucasmenendez committed Mar 27, 2024
1 parent eaee259 commit c0e333b
Show file tree
Hide file tree
Showing 9 changed files with 343 additions and 187 deletions.
37 changes: 21 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ package main

import (
"log"
"math/rand"

"github.com/lucasmenendez/gosss"
)
Expand All @@ -38,8 +37,8 @@ func main() {
// create a configuration with 8 shares and 7 minimum shares to recover the
// message
config := &gosss.Config{
Shares: 4,
Min: 3,
Shares: 9,
Min: 6,
}
// hide a message with the defined configuration
msg := "688641b753f1c97526d6a767058a80fd6c6519f5bdb0a08098986b0478c8502b"
Expand All @@ -49,25 +48,31 @@ func main() {
log.Fatalf("error hiding message: %v", err)
}
// print every share and exclude one share to test the recovery
requiredShares := [][]string{}
for _, secretShares := range totalShares {
log.Printf("shares: %v", secretShares)
// choose a random share to exclude
index := rand.Intn(len(secretShares))
shares := []string{}
for i, share := range secretShares {
if i == index {
continue
}
shares = append(shares, share)
requiredShares := []string{}
completedSecrets := map[int][]string{}
for _, share := range totalShares {
// choose some shares for each secret until reach the minimum
secret, err := gosss.ShareSecret(share)
if err != nil {
log.Fatalf("error sharing secret: %v", err)
}
requiredShares = append(requiredShares, shares)
current, ok := completedSecrets[secret]
if !ok || len(current) < 2 {
completedSecrets[secret] = append(completedSecrets[secret], share)
requiredShares = append(requiredShares, share)
log.Printf("selected share: %s", share)
continue
}
log.Printf("discared share: %s", share)
}
// recover the message with the required shares
// recover the message with the required shares, the configuration is not
// needed because the shares have the information to recover the message and
// default prime number is used during the hiding process
message, err := gosss.RecoverMessage(requiredShares, nil)
if err != nil {
log.Fatalf("error recovering message: %v", err)
}
log.Printf("recovered message: %s", string(message))
}

```
96 changes: 58 additions & 38 deletions config.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
package gosss

import "math/big"

// 12th Mersenne Prime (2^127 - 1)
var DefaultPrime *big.Int = new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(127), nil), big.NewInt(1))

// maxShares defines the maximum number of shares that can be generated
const maxShares = 256
import (
"fmt"
"math"
"math/big"
)

// operation type defines the operation to perform with the configuration
type operation int
// bn254 𝔽r
var DefaultPrime, _ = new(big.Int).SetString("21888242871839275222246405745257275088548364400416034343698204186575808495617", 10)

const (
// hideOp constant defines the operation to hide a message
hideOp operation = iota
// recoverOp constant defines the operation to recover a message
recoverOp
)
// maxShares defines the maximum number of shares that can be generated, indexed
// using 2 bytes (255^2)
const maxShares = 65536

// Config struct defines the configuration for the Shamir Secret Sharing
// algorithm. It includes the number of shares to generate, the minimum number
Expand All @@ -25,35 +20,42 @@ type Config struct {
Shares int
Min int
Prime *big.Int
nParts float64
}

// prepare checks if the configuration is valid for the operation to perform and
// sets the default prime number if it is not defined.
func (c *Config) prepare(op operation) error {
switch op {
case hideOp:
// a config is valid for hide a message if the number of shares are
// greater than 0 and lower or equal to the maximum number of shares,
// and the minimum number of shares is greater than 1 and lower than
// the number of shares
if c.Shares <= 0 || c.Shares > maxShares {
return ErrConfigShares
}
if c.Min <= 1 || c.Min >= c.Shares {
return ErrConfigMin
}
case recoverOp:
// for recover a message no checks are needed unless the prime number is
// not defined so break the switch and set the default prime number if
// it is needed
break
default:
return ErrConfigOp
}
// prepare sets the prime number to use as finite field if it is not defined.
func (c *Config) prepare() {
// if the prime number is not defined it will use the default prime number
if c.Prime == nil {
c.Prime = DefaultPrime
}
}

// checkParts validates the number of shares and minimum shares to recover the
// original message based on the number of parts the message is divided into. It
// returns an error if the configuration is invalid. If the configuration is
// valid, it sets the number of parts the message is divided into in the config
// struct and returns nil. The config must fit the following conditions:
// - The number of shares must be between 3 * number of parts and 65536, a
// constat defined by a maximum of 2 bytes to index the shares (255^2).
// - The number of shares must be divisible by the number of parts, to ensure
// the shares are generated correctly.
// - The minimum number of shares to recover the secret must be between
// 2 * number of parts and number of shares - number of parts.
func (c *Config) checkParts(n int) error {
minShares := n * 3
if c.Shares < minShares || c.Shares > maxShares {
return fmt.Errorf("%w (%d-%d)", ErrConfigShares, minShares, maxShares)
}
if c.Shares%n != 0 {
return fmt.Errorf("%w, must be divisible by %d (number of message parts)", ErrConfigShares, n)
}
minMin := n * 2
maxMin := c.Shares - n
if c.Min < minMin || c.Min > maxMin {
return fmt.Errorf("%w (%d-%d)", ErrConfigMin, minMin, maxMin)
}
c.nParts = float64(n)
return nil
}

Expand All @@ -63,3 +65,21 @@ func (c *Config) prepare(op operation) error {
func (c *Config) maxSecretPartSize() int {
return len(c.Prime.Bytes()) - 1
}

// minByPart returns the minimum number of shares to recover the secret part, it
// is the minimum number of shares divided by the number of parts.
func (c *Config) minByPart() int {
if c.nParts == 0 {
return 0
}
return int(math.Floor(float64(c.Min) / c.nParts))
}

// sharesByPart returns the number of shares to generate for each part of the
// message, it is the number of shares divided by the number of parts.
func (c *Config) sharesByPart() int {
if c.nParts == 0 {
return 0
}
return int(math.Floor(float64(c.Shares) / c.nParts))
}
130 changes: 70 additions & 60 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,85 +6,95 @@ import (
)

func Test_prepare(t *testing.T) {
noMin := &Config{
Shares: 8,
Min: 0,
c := Config{}
c.prepare()
if c.Prime.Cmp(DefaultPrime) != 0 {
t.Errorf("expected %v, got %v", DefaultPrime, c.Prime)
}
if err := noMin.prepare(hideOp); err == nil {
t.Errorf("expected error")
return
newPrime := big.NewInt(103)
c.Prime = new(big.Int).Set(newPrime)
c.prepare()
if c.Prime.Cmp(newPrime) != 0 {
t.Errorf("expected %v, got %v", newPrime, c.Prime)
}
if err := noMin.prepare(recoverOp); err != nil {
t.Errorf("unexpected error: %v", err)
}

func Test_checkParts(t *testing.T) {
// valid configuration
validConfig := Config{Shares: 12, Min: 8, Prime: big.NewInt(13)}
if err := validConfig.checkParts(4); err != nil {
t.Errorf("Valid Configuration: Expected no error, got %v", err)
return
}
oneMin := &Config{
Shares: 8,
Min: 1,
}
if err := oneMin.prepare(hideOp); err == nil {
t.Errorf("expected error")
// shares less than minimum
lessThanMinSharesConfig := Config{Shares: 2, Min: 1, Prime: big.NewInt(13)}
if err := lessThanMinSharesConfig.checkParts(1); err == nil {
t.Errorf("shares less than minimum: Expected error, got none")
return
}
if err := oneMin.prepare(recoverOp); err != nil {
t.Errorf("unexpected error: %v", err)
// shares greater than maximum
greaterThanMaxSharesConfig := Config{Shares: maxShares + 1, Min: 20000, Prime: big.NewInt(13)}
if err := greaterThanMaxSharesConfig.checkParts(10000); err == nil {
t.Errorf("shares Greater than maximum: Expected error, got none")
return
}
wrongMin := &Config{
Shares: 8,
Min: 9,
}
if err := wrongMin.prepare(hideOp); err == nil {
t.Errorf("expected error")
// hares Not Divisible by parts
sharesNotDivisibleConfig := Config{Shares: 10, Min: 5, Prime: big.NewInt(13)}
if err := sharesNotDivisibleConfig.checkParts(3); err == nil {
t.Errorf("shares Not Divisible by parts: Expected error, got none")
return
}
if err := wrongMin.prepare(recoverOp); err != nil {
t.Errorf("unexpected error: %v", err)
// inimum shares less than allowed
minSharesLessThanAllowedConfig := Config{Shares: 12, Min: 3, Prime: big.NewInt(13)}
if err := minSharesLessThanAllowedConfig.checkParts(4); err == nil {
t.Errorf("Minimum shares less than allowed: Expected error, got none")
return
}
noShares := &Config{
Shares: 0,
Min: 7,
}
if err := noShares.prepare(hideOp); err == nil {
t.Errorf("expected error")
// inimum shares Greater than allowed
minSharesGreaterThanAllowedConfig := Config{Shares: 12, Min: 10, Prime: big.NewInt(13)}
if err := minSharesGreaterThanAllowedConfig.checkParts(4); err == nil {
t.Errorf("Minimum shares Greater than allowed: Expected error, got none")
return
}
if err := noShares.prepare(recoverOp); err != nil {
t.Errorf("unexpected error: %v", err)
return
}
maxShares := &Config{
Shares: 257,
Min: 7,
}

func Test_maxSecretPartSize(t *testing.T) {
c := Config{Prime: big.NewInt(13)}
if c.maxSecretPartSize() != 0 {
t.Errorf("expected 0, got %d", c.maxSecretPartSize())
}
if err := maxShares.prepare(hideOp); err == nil {
t.Errorf("expected error")
return
c.Prime = big.NewInt(1000000000000000000)
if c.maxSecretPartSize() != 7 {
t.Errorf("expected 7, got %d", c.maxSecretPartSize())
}
if err := maxShares.prepare(recoverOp); err != nil {
t.Errorf("unexpected error: %v", err)
return
}

func Test_minByPart(t *testing.T) {
c := Config{Min: 8}
if c.minByPart() != 0 {
t.Errorf("expected 0, got %d", c.minByPart())
}
basicConf := &Config{
Shares: 8,
Min: 7,
c.nParts = 4
if c.minByPart() != 2 {
t.Errorf("expected 2, got %d", c.minByPart())
}
if err := basicConf.prepare(hideOp); err != nil {
t.Errorf("unexpected error: %v", err)
return
c.nParts = 0
if c.minByPart() != 0 {
t.Errorf("expected 0, got %d", c.minByPart())
}
if basicConf.Prime.Cmp(DefaultPrime) != 0 {
t.Errorf("unexpected prime number: %v", basicConf.Prime)
return
}

func Test_sharesByPart(t *testing.T) {
c := Config{Shares: 12}
if c.sharesByPart() != 0 {
t.Errorf("expected 0, got %d", c.sharesByPart())
}
basicConf.Prime = big.NewInt(1003)
if err := basicConf.prepare(recoverOp); err != nil {
t.Errorf("unexpected error: %v", err)
return
c.nParts = 4
if c.sharesByPart() != 3 {
t.Errorf("expected 3, got %d", c.sharesByPart())
}
if basicConf.Prime.Cmp(big.NewInt(1003)) != 0 {
t.Errorf("unexpected prime number: %v", basicConf.Prime)
return
c.nParts = 0
if c.sharesByPart() != 0 {
t.Errorf("expected 0, got %d", c.sharesByPart())
}
}
}
Loading

0 comments on commit c0e333b

Please sign in to comment.