package keyset import ( "encoding/binary" "errors" "git.xdrm.io/schastsp/context" "git.xdrm.io/schastsp/pkg/scha" "io" ) const SecretSize = scha.HSIZE * 4 /* Attributes */ type T struct { ctx *context.T // current context depth uint16 // cur depth sec []byte // secret mcode uint8 // migration code (secret renewal) // 0: none // 1: need to migrate // 2: waiting migration } /* (1) Creates a new KeySet * * @ctx Context constants * ---------------------------------------------------------*/ func Create(ctx *context.T) (*T, error) { /* (1) Fail if min+thre >= max */ if ctx == nil { return nil, errors.New("Context must not be null") } /* (2) Instanciate */ var instance = new(T) /* (3) Set attributes */ instance.ctx = ctx /* (4) Generate values if secret */ if instance.sec == nil { instance.generate() } return instance, nil } /* (2) Get current hash * * @return digest<[]byte]> Current hash representing the set * ---------------------------------------------------------*/ func (s T) CurrentHash() ([]byte, error) { /* (1) Get digest */ digest, err := scha.Hash(s.sec, s.depth) /* (2) Dispatch error */ if err != nil { return nil, err } /* (3) Else -> return digest */ return digest, nil } /* (3) Decrement depth * * @return remaining Remaining hashes before migration * ---------------------------------------------------------*/ func (s *T) Decrement() uint16 { /* (1) Decrement the depth */ s.depth-- /* (2) If near minDepth (10 far): set consumption to 1 */ if s.mcode == 0 && s.depth <= s.ctx.MinDepth()+s.ctx.DepthThreshold() { s.mcode = 1 } /* (3) Return remaining attempts */ return s.depth - s.ctx.MinDepth() } /* (4) Serialisation * * @return serial String representation * * * === FORMAT === * * | hsize | secret | depth | * +-----------+------------+---------+ * | 16 bits | hsize bits | 16 bits | * * == ENDIANNESS == * * network endianness -> big-endian * ---------------------------------------------------------*/ func (s *T) Store(writer io.Writer) error { var err error /* (1) Copy secret size */ err = binary.Write(writer, binary.BigEndian, uint16(len(s.sec))) if err != nil { return err } /* (2) Copy secret */ err = binary.Write(writer, binary.BigEndian, s.sec) if err != nil { return err } /* (3) Copy depth */ err = binary.Write(writer, binary.BigEndian, s.depth) if err != nil { return err } /* (4) Copy migration code */ err = binary.Write(writer, binary.BigEndian, s.mcode) if err != nil { return err } return nil } /* (5) Builds a KeySet from its serial representation * * @serial String representation * * @return instance<*Set> The corresponding set * @return err Optional error * ---------------------------------------------------------*/ func (s *T) Fetch(reader io.Reader) error { var err error var secretLength uint16 /* (1) Read the secret size */ err = binary.Read(reader, binary.BigEndian, &secretLength) if err != nil { return err } /* (2) Fail if secretLength lower than digest size */ if secretLength < scha.HSIZE { return errors.New("Invalid secret size (must be at least the same size as a digest)") } /* (3) Try to copy the secret */ s.sec = make([]byte, secretLength) err = binary.Read(reader, binary.BigEndian, &s.sec) if err != nil { return err } /* (4) Manage invalid secret size (mismatch secretLength) */ if uint16(len(s.sec)) != secretLength { return errors.New("Mismatching secret size") } /* (5) Try to copy the depth */ err = binary.Read(reader, binary.BigEndian, &s.depth) if err != nil { return err } /* (6) Try to copy the migration code */ err = binary.Read(reader, binary.BigEndian, &s.mcode) if err != nil { return err } return nil } /* (6) Getter/Setter for migration code 'mcode' * * @mcode [OPT] New value * * @return mcode Migration code * ---------------------------------------------------------*/ func (s *T) MigrationCode(optional ...uint8) uint8 { /* (1) If no valid code given -> return current one */ if len(optional) < 1 || optional[0] > 2 { return s.mcode } /* (2) Set new code */ s.mcode = optional[0] /* (3) Return new code */ return s.mcode } /* (7) Updates depth for rescuing from desynchroisation * * @lastHash<[]byte> Last received hash * * @return error Error or NIL if not * * @description We must update @depth so the next sent hash h_depth(k) * hashed MIN times equals the received hash @lastHash * We must find depth = i-MIN+1 * with i so that h_MIN( h_i(k) ) = lastHash * ---------------------------------------------------------*/ func (s *T) Rescue(lastHash []byte) error { /* (1) Browse possible values ---------------------------------------------------------*/ for i := s.depth; i <= s.depth+s.ctx.MinDepth(); i++ { /* (1) Process hash */ currentHash, err := scha.Hash(s.sec, i) if err != nil { return err } /* (2) If not found -> try again */ if string(currentHash) != string(lastHash) { continue } /* (3) Store new depth */ s.depth = i - s.ctx.MinDepth() + 1 /* (4) Update migration code */ s.mcode = 2 return nil } return errors.New("Cannot find an available rescue depth") }