253 lines
5.2 KiB
Go
253 lines
5.2 KiB
Go
package keyset
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"git.xdrm.io/schastsp/context"
|
|
"git.xdrm.io/schastsp/util/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> 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 <nil> 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<uint> 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> 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> String representation
|
|
*
|
|
* @return instance<*Set> The corresponding set
|
|
* @return err<error> 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<uint8> [OPT] New value
|
|
*
|
|
* @return mcode<uint8> 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<err> 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")
|
|
|
|
}
|