feat: fcm

This commit is contained in:
x88 2020-07-28 17:52:05 +03:00
parent 953e95bdc0
commit 2bb4ad47ad
16 changed files with 990 additions and 0 deletions

View File

@ -0,0 +1,97 @@
package main
import (
"fmt"
"github.com/looplab/fsm"
)
func main() {
signatureProposalFSM := fsm.NewFSM(
"idle",
fsm.Events{
{Name: "proposal_spotted", Src: []string{"idle"}, Dst: "validate_proposal"},
{Name: "proposal_valid", Src: []string{"validate_proposal"}, Dst: "proposed"},
{Name: "proposal_invalid", Src: []string{"validate_proposal"}, Dst: "idle"},
{Name: "recieve_yay", Src: []string{"proposed"}, Dst: "process_yay"},
{Name: "receive_nay", Src: []string{"proposed"}, Dst: "process_nay"},
{Name: "send_nay", Src: []string{"proposed"}, Dst: "proposed"},
{Name: "send_yay", Src: []string{"proposed"}, Dst: "proposed"},
{Name: "enough_yays", Src: []string{"process_yay"}, Dst: "signing"},
{Name: "enough_nays", Src: []string{"process_nay"}, Dst: "abort"},
{Name: "not_enough_yays", Src: []string{"process_yay"}, Dst: "proposed"},
{Name: "not_enough_nays", Src: []string{"process_nay"}, Dst: "proposed"},
},
fsm.Callbacks{},
)
fmt.Print(fsm.Visualize(signatureProposalFSM))
signatureConstructFSM := fsm.NewFSM(
"idle",
fsm.Events{
{Name: "request_airgapped_sig", Src: []string{"signing"}, Dst: "signing"},
{Name: "transmit_airgapped_sig", Src: []string{"signing"}, Dst: "signing"},
{Name: "receive_sig", Src: []string{"signing"}, Dst: "process_sig"},
{Name: "enough_signature_shares", Src: []string{"process_sig"}, Dst: "reconstruct_signature"},
{Name: "not_enough_signature_shares", Src: []string{"process_sig"}, Dst: "signing"},
{Name: "signature_reconstucted", Src: []string{"reconstruct_signature"}, Dst: "publish_signature"},
{Name: "signature_published", Src: []string{"publish_signature"}, Dst: "fin"},
{Name: "failed_to_reconstuct_signature", Src: []string{"reconstruct_signature"}, Dst: "signing"},
},
fsm.Callbacks{},
)
fmt.Print(fsm.Visualize(signatureConstructFSM))
DkgProposeFSM := fsm.NewFSM(
"idle",
fsm.Events{
{Name: "proposal_spotted", Src: []string{"idle"}, Dst: "validate_proposal"},
{Name: "proposal_valid", Src: []string{"validate_proposal"}, Dst: "proposed"},
{Name: "proposal_invalid", Src: []string{"validate_proposal"}, Dst: "idle"},
{Name: "recieve_yay", Src: []string{"proposed"}, Dst: "process_yay"},
{Name: "receive_nay", Src: []string{"proposed"}, Dst: "abort"},
{Name: "send_nay", Src: []string{"proposed"}, Dst: "proposed"},
{Name: "send_yay", Src: []string{"proposed"}, Dst: "proposed"},
{Name: "not_enough_yays", Src: []string{"process_yay"}, Dst: "proposed"},
{Name: "all_yays", Src: []string{"process_yay"}, Dst: "dkg_commitments"},
{Name: "timeout", Src: []string{"proposed"}, Dst: "abort"},
},
fsm.Callbacks{},
)
fmt.Print(fsm.Visualize(DkgProposeFSM))
DkgCommitFSM := fsm.NewFSM(
"dkg_commitments",
fsm.Events{
{Name: "request_airgapped_commitment", Src: []string{"dkg_commitments"}, Dst: "dkg_commitments"},
{Name: "transmit_airgapped_commitment", Src: []string{"dkg_commitments"}, Dst: "dkg_commitments"},
{Name: "recieve_commitment", Src: []string{"dkg_commitments"}, Dst: "process_commitment"},
{Name: "invalid_commitment", Src: []string{"process_commitment"}, Dst: "abort"},
{Name: "all_commitments", Src: []string{"process_commitment"}, Dst: "dkg_deals"},
{Name: "not_enough_commitments", Src: []string{"process_commitment"}, Dst: "dkg_commitments"},
{Name: "timeout", Src: []string{"dkg_commitments"}, Dst: "abort"},
},
fsm.Callbacks{},
)
fmt.Print(fsm.Visualize(DkgCommitFSM))
DkgDealsFSM := fsm.NewFSM(
"dkg_deals",
fsm.Events{
{Name: "pass_commitements_and_request_airgapped_deals", Src: []string{"dkg_deals"}, Dst: "dkg_deals"},
{Name: "transmit_airgapped_deals", Src: []string{"dkg_deals"}, Dst: "dkg_deals"},
{Name: "transmit_airgapped_error", Src: []string{"dkg_deals"}, Dst: "abort"},
{Name: "recieve_deal", Src: []string{"dkg_deals"}, Dst: "process_deal"},
{Name: "not_my_deal", Src: []string{"process_deal"}, Dst: "dkg_deals"},
{Name: "invalid_deal", Src: []string{"process_deal"}, Dst: "abort"},
{Name: "enough_deals", Src: []string{"process_deal"}, Dst: "dkg_construct_tss"},
{Name: "not_enough_deals", Src: []string{"process_deal"}, Dst: "dkg_deals"},
{Name: "pass_deals_and_request_airgapped_public_key", Src: []string{"dkg_construct_tss"}, Dst: "dkg_construct_tss"},
{Name: "transmit_airgapped_public_key", Src: []string{"dkg_construct_tss"}, Dst: "fin"},
{Name: "transmit_airgapped_error", Src: []string{"dkg_construct_tss"}, Dst: "abort"},
{Name: "timeout", Src: []string{"dkg_deals"}, Dst: "abort"},
},
fsm.Callbacks{},
)
fmt.Print(fsm.Visualize(DkgDealsFSM))
}

34
fsm/cmd/test/test.go Normal file
View File

@ -0,0 +1,34 @@
package main
import (
"github.com/p2p-org/dc4bc/fsm/state_machines"
"github.com/p2p-org/dc4bc/fsm/types/requests"
"log"
)
func main() {
fsmMachine, err := state_machines.New([]byte{})
log.Println(fsmMachine, err)
resp, dump, err := fsmMachine.Do(
"proposal_init",
"d8a928b2043db77e340b523547bf16cb4aa483f0645fe0a290ed1f20aab76257",
requests.ProposalParticipantsListRequest{
{
"John Doe",
[]byte("pubkey123123"),
},
{
"Crypto Billy",
[]byte("pubkey456456"),
},
{
"Matt",
[]byte("pubkey789789"),
},
},
)
log.Println("Response", resp)
log.Println("Err", err)
log.Println("Dump", string(dump))
}

6
fsm/config/config.go Normal file
View File

@ -0,0 +1,6 @@
package config
const (
// TODO: Move to machine level configs?
ParticipantsMinCount = 3
)

308
fsm/fsm/fsm.go Normal file
View File

@ -0,0 +1,308 @@
package fsm
import (
"errors"
"sync"
)
//
// fsmInstance, err := fsm.New(scope)
// if err != nil {
// log.Println(err)
// return
// }
//
// fsmInstance.Do(event, args)
//
// Temporary global finish state for deprecating operations
const (
StateGlobalIdle = "__idle"
StateGlobalDone = "__done"
)
// FSMResponse returns result for processing with client events
type FSMResponse struct {
// Returns machine execution result state
State string
// Must be cast, according to mapper event_name->response_type
Data interface{}
}
type FSM struct {
name string
initialState string
currentState string
// May be mapping must require pair source + event?
transitions map[trKey]*trEvent
callbacks Callbacks
initialEvent string
// Finish states, for switch machine or fin,
// These states cannot be linked as SrcState in this machine
finStates map[string]bool
// stateMu guards access to the currentState state.
stateMu sync.RWMutex
// eventMu guards access to State() and Transition().
eventMu sync.Mutex
}
// Transition key source + dst
type trKey struct {
source string
event string
}
// Transition lightweight event description
type trEvent struct {
dstState string
isInternal bool
}
type EventDesc struct {
Name string
SrcState []string
// Dst state changes after callback
DstState string
// Internal events, cannot be emitted from external call
IsInternal bool
}
type Callback func(event string, args ...interface{}) (interface{}, error)
type Callbacks map[string]Callback
// TODO: Exports
func MustNewFSM(name, initial string, events []EventDesc, callbacks map[string]Callback) *FSM {
// Add validation, chains building
if name == "" {
panic("name cannot be empty")
}
if initial == "" {
panic("initialState state cannot be empty")
}
// to remove
if len(events) == 0 {
panic("cannot init fsm with empty events")
}
f := &FSM{
name: name,
currentState: initial,
initialState: initial,
transitions: make(map[trKey]*trEvent),
finStates: make(map[string]bool),
callbacks: make(map[string]Callback),
}
allEvents := make(map[string]bool)
// Required for find finStates
allSources := make(map[string]bool)
allStates := make(map[string]bool)
// Validate events
for _, event := range events {
if event.Name == "" {
panic("cannot init empty event")
}
// TODO: Check transition when all events added
if len(event.SrcState) == 0 {
panic("event must have min one source available state")
}
if event.DstState == "" {
panic("event dest cannot be empty, use StateGlobalDone for finish or external state")
}
if _, ok := allEvents[event.Name]; ok {
panic("duplicate event")
}
allEvents[event.Name] = true
allStates[event.DstState] = true
for _, sourceState := range event.SrcState {
tKey := trKey{
sourceState,
event.Name,
}
if sourceState == StateGlobalDone {
panic("StateGlobalDone cannot set as source state")
}
if _, ok := f.transitions[tKey]; ok {
panic("duplicate dst for pair `source + event`")
}
f.transitions[tKey] = &trEvent{event.DstState, event.IsInternal}
// For using provider, event must use with IsGlobal = true
if sourceState == initial {
if f.initialEvent != "" {
panic("machine entry event already exist")
}
f.initialEvent = event.Name
}
allSources[sourceState] = true
}
}
if len(allStates) < 2 {
panic("machine must contain at least two states")
}
// Validate callbacks
for event, callback := range callbacks {
if event == "" {
panic("callback name cannot be empty")
}
if _, ok := allEvents[event]; !ok {
panic("callback has no event")
}
f.callbacks[event] = callback
}
for state := range allStates {
if state == StateGlobalIdle {
continue
}
// Exit states cannot be a source in this machine
if _, exists := allSources[state]; !exists || state == StateGlobalDone {
f.finStates[state] = true
}
}
if len(f.finStates) == 0 {
panic("cannot initialize machine without final states")
}
return f
}
func (f *FSM) Do(event string, args ...interface{}) (resp *FSMResponse, err error) {
f.eventMu.Lock()
defer f.eventMu.Unlock()
trEvent, ok := f.transitions[trKey{f.currentState, event}]
if !ok {
return nil, errors.New("cannot execute event for this state")
}
if trEvent.isInternal {
return nil, errors.New("event is internal")
}
resp = &FSMResponse{
State: f.State(),
}
if callback, ok := f.callbacks[event]; ok {
resp.Data, err = callback(event, args...)
// Do not try change state on error
if err != nil {
return resp, err
}
}
err = f.setState(event)
return
}
// State returns the currentState state of the FSM.
func (f *FSM) State() string {
f.stateMu.RLock()
defer f.stateMu.RUnlock()
return f.currentState
}
// setState allows the user to move to the given state from currentState state.
// The call does not trigger any callbacks, if defined.
func (f *FSM) setState(event string) error {
f.stateMu.Lock()
defer f.stateMu.Unlock()
trEvent, ok := f.transitions[trKey{f.currentState, event}]
if !ok {
return errors.New("cannot change state")
}
f.currentState = trEvent.dstState
return nil
}
func (f *FSM) Name() string {
return f.name
}
func (f *FSM) InitialState() string {
return f.initialState
}
// Check entry event for available emitting as global entry event
func (f *FSM) GlobalInitialEvent() (event string) {
if initialEvent, exists := f.transitions[trKey{StateGlobalIdle, f.initialEvent}]; exists {
if !initialEvent.isInternal {
event = f.initialEvent
}
}
return
}
func (f *FSM) EntryEvent() (event string) {
if entryEvent, exists := f.transitions[trKey{f.initialState, f.initialEvent}]; exists {
if !entryEvent.isInternal {
event = f.initialEvent
}
}
return
}
func (f *FSM) EventsList() (events []string) {
if len(f.transitions) > 0 {
for trKey, trEvent := range f.transitions {
if !trEvent.isInternal {
events = append(events, trKey.event)
}
}
}
return
}
func (f *FSM) StatesList() (states []string) {
allStates := map[string]bool{}
if len(f.transitions) > 0 {
for trKey, _ := range f.transitions {
allStates[trKey.source] = true
}
}
if len(allStates) > 0 {
for state := range allStates {
states = append(states, state)
}
}
return
}
func (f *FSM) IsFinState(state string) bool {
_, exists := f.finStates[state]
return exists
}

150
fsm/fsm_pool/fsm_pool.go Normal file
View File

@ -0,0 +1,150 @@
package fsm_pool
import (
"errors"
"github.com/p2p-org/dc4bc/fsm/fsm"
)
type IStateMachine interface {
// Returns machine state from scope dump
// For nil argument returns fsm with process initiation
// Get() IStateMachine
Name() string
InitialState() string
// Process event
Do(event string, args ...interface{}) (*fsm.FSMResponse, error)
GlobalInitialEvent() string
EventsList() []string
StatesList() []string
IsFinState(state string) bool
}
type FSMMapper map[string]IStateMachine
type FSMRouteMapper map[string]string
type FSMPoolProvider struct {
fsmInitialEvent string
// Pool mapper by names
mapper FSMMapper
events FSMRouteMapper
states FSMRouteMapper
}
func Init(machines ...IStateMachine) *FSMPoolProvider {
if len(machines) == 0 {
panic("cannot initialize empty pool")
}
p := &FSMPoolProvider{
mapper: make(FSMMapper),
events: make(FSMRouteMapper),
states: make(FSMRouteMapper),
}
allInitStatesMap := make(map[string]string)
// Fill up mapper
for _, machine := range machines {
if machine == nil {
panic("machine not initialized, got nil")
}
machineName := machine.Name()
if machineName == "" {
panic("machine name cannot be empty")
}
if _, exists := p.mapper[machineName]; exists {
panic("duplicate machine name")
}
allInitStatesMap[machine.InitialState()] = machineName
machineEvents := machine.EventsList()
for _, event := range machineEvents {
if _, exists := p.events[event]; exists {
panic("duplicate public event")
}
p.events[event] = machineName
}
// Setup entry event for machines pool if available
if initialEvent := machine.GlobalInitialEvent(); initialEvent != "" {
if p.fsmInitialEvent != "" {
panic("duplicate entry event initialization")
}
p.fsmInitialEvent = initialEvent
}
p.mapper[machineName] = machine
}
// Second iteration, all initial states filled up
// Fill up states with initial and exit states checking
for _, machine := range machines {
machineName := machine.Name()
machineStates := machine.StatesList()
for _, state := range machineStates {
if machine.IsFinState(state) {
// If state is initial for another machine,
if initMachineName, exists := allInitStatesMap[state]; exists {
p.states[allInitStatesMap[state]] = initMachineName
continue
}
}
if name, exists := p.states[state]; exists && name != machineName {
panic("duplicate state for machines")
}
p.states[state] = machineName
}
}
if p.fsmInitialEvent == "" {
panic("machines pool entry event not set")
}
return p
}
func (p *FSMPoolProvider) EntryPointMachine() (IStateMachine, error) {
// StateGlobalIdle
// TODO: Short code
entryStateMachineName := p.events[p.fsmInitialEvent]
machine, exists := p.mapper[entryStateMachineName]
if !exists || machine == nil {
return nil, errors.New("cannot init machine with entry point")
}
return machine, nil
}
func (p *FSMPoolProvider) MachineByEvent(event string) (IStateMachine, error) {
eventMachineName := p.events[event]
machine, exists := p.mapper[eventMachineName]
if !exists || machine == nil {
return nil, errors.New("cannot init machine for event")
}
return machine, nil
}
func (p *FSMPoolProvider) MachineByState(state string) (IStateMachine, error) {
eventMachineName := p.states[state]
machine, exists := p.mapper[eventMachineName]
if !exists || machine == nil {
return nil, errors.New("cannot init machine for state")
}
return machine, nil
}

View File

@ -0,0 +1 @@
package dkg_commit_fsm

View File

@ -0,0 +1 @@
package dkg_deals_fsm

View File

@ -0,0 +1 @@
package dkg_proposal_fsm

View File

@ -0,0 +1,13 @@
package internal
type MachineStatePayload struct {
ProposalPayload ProposalConfirmationPrivateQuorum
SigningPayload map[string]interface{}
}
// Using combine response for modify data with chain
// User value or pointer? How about memory state?
type MachineCombinedResponse struct {
Response interface{}
Payload *MachineStatePayload
}

View File

@ -0,0 +1,17 @@
package internal
import "time"
type ProposalParticipantPrivate struct {
// Public title for address, such as name, nickname, organization
Title string
PublicKey []byte
// For validation user confirmation: sign(InvitationSecret, PublicKey) => user
InvitationSecret string
ConfirmedAt *time.Time
}
// Unique alias for map iteration - Public Key Fingerprint
// Excludes array merge and rotate operations
type ProposalConfirmationPrivateQuorum map[string]ProposalParticipantPrivate

View File

@ -0,0 +1,98 @@
package state_machines
import (
"encoding/json"
"errors"
"github.com/p2p-org/dc4bc/fsm/fsm"
"github.com/p2p-org/dc4bc/fsm/fsm_pool"
"github.com/p2p-org/dc4bc/fsm/state_machines/internal"
"github.com/p2p-org/dc4bc/fsm/state_machines/signature_construct_fsm"
"github.com/p2p-org/dc4bc/fsm/state_machines/signature_proposal_fsm"
)
// Is machine state scope dump will be locked?
type FSMDump struct {
Id string
State string
Payload internal.MachineStatePayload
}
type FSMInstance struct {
machine fsm_pool.IStateMachine
dump *FSMDump
}
var (
fsmPoolProvider *fsm_pool.FSMPoolProvider
)
func init() {
fsmPoolProvider = fsm_pool.Init(
signature_proposal_fsm.New(),
signature_construct_fsm.New(),
)
}
func New(data []byte) (*FSMInstance, error) {
var err error
i := &FSMInstance{}
if len(data) == 0 {
i.InitDump()
i.machine, err = fsmPoolProvider.EntryPointMachine()
return i, err // Create machine
}
err = i.dump.Unmarshal(data)
if err != nil {
return nil, errors.New("cannot read machine dump")
}
i.machine, err = fsmPoolProvider.MachineByState(i.dump.State)
return i, err
}
func (i *FSMInstance) Do(event string, args ...interface{}) (*fsm.FSMResponse, []byte, error) {
// Provide payload as first argument ever
result, err := i.machine.Do(event, append([]interface{}{i.dump.Payload}, args...)...)
// On route errors result will be nil
if result != nil {
// Proxying combined response, separate payload and data
if result.Data != nil {
if r, ok := result.Data.(internal.MachineCombinedResponse); ok {
i.dump.Payload = *r.Payload
result.Data = r.Response
} else {
return nil, []byte{}, errors.New("cannot cast callback response")
}
}
i.dump.State = result.State
}
dump, dumpErr := i.dump.Marshal()
if dumpErr != nil {
return result, []byte{}, err
}
return result, dump, err
}
func (i *FSMInstance) InitDump() {
if i.dump == nil {
i.dump = &FSMDump{
State: fsm.StateGlobalIdle,
}
}
}
// TODO: Add encryption
func (d *FSMDump) Marshal() ([]byte, error) {
return json.Marshal(d)
}
// TODO: Add decryption
func (d *FSMDump) Unmarshal(data []byte) error {
return json.Unmarshal(data, d)
}

View File

@ -0,0 +1,39 @@
package signature_construct_fsm
import (
"github.com/p2p-org/dc4bc/fsm/fsm"
"github.com/p2p-org/dc4bc/fsm/fsm_pool"
)
const (
fsmName = "signature_construct_fsm"
stateConstructorEntryPoint = "process_sig"
awaitConstructor = "validate_process_sig" // waiting participants
eventInitSignatureConstructor = "process_sig_init"
eventInitSignatureFinishTmp = "process_sig_fin"
)
type SignatureConstructFSM struct {
*fsm.FSM
}
func New() fsm_pool.IStateMachine {
machine := &SignatureConstructFSM{}
machine.FSM = fsm.MustNewFSM(
fsmName,
stateConstructorEntryPoint,
[]fsm.EventDesc{
// {Name: "", SrcState: []string{""}, DstState: ""},
// Init
{Name: eventInitSignatureConstructor, SrcState: []string{stateConstructorEntryPoint}, DstState: awaitConstructor},
{Name: eventInitSignatureFinishTmp, SrcState: []string{awaitConstructor}, DstState: "dkg_proposal_fsm"},
},
fsm.Callbacks{},
)
return machine
}

View File

@ -0,0 +1,98 @@
package signature_proposal_fsm
import (
"errors"
"github.com/p2p-org/dc4bc/fsm/state_machines/internal"
"github.com/p2p-org/dc4bc/fsm/types/requests"
"github.com/p2p-org/dc4bc/fsm/types/responses"
"log"
)
// init -> awaitingConfirmations
// args: payload, signing id, participants list
func (s *SignatureProposalFSM) actionInitProposal(event string, args ...interface{}) (response interface{}, err error) {
var payload internal.MachineStatePayload
// Init proposal
log.Println("I'm actionInitProposal")
if len(args) < 3 {
err = errors.New("payload and signing id required and participants list required")
return
}
if len(args) > 3 {
err = errors.New("too many arguments")
return
}
payload, ok := args[0].(internal.MachineStatePayload)
if !ok {
err = errors.New("cannot cast payload")
return
}
signingId, ok := args[1].(string)
if !ok {
err = errors.New("cannot cast signing id, awaiting string value")
return
}
if len(signingId) < signingIdLen {
err = errors.New("signing id to short ")
return
}
request, ok := args[2].(requests.ProposalParticipantsListRequest)
if !ok {
err = errors.New("cannot cast participants list")
return
}
if err = request.Validate(); err != nil {
return
}
payload.ProposalPayload = make(internal.ProposalConfirmationPrivateQuorum)
for _, participant := range request {
participantId := createFingerprint(&participant.PublicKey)
secret, err := generateRandomString(32)
if err != nil {
return nil, errors.New("cannot generateRandomString")
}
payload.ProposalPayload[participantId] = internal.ProposalParticipantPrivate{
Title: participant.Title,
PublicKey: participant.PublicKey,
InvitationSecret: secret,
ConfirmedAt: nil,
}
}
/*s.state = &fsm_pool.FSMachine{
Id: signingId,
State: stateAwaitProposalConfirmation,
}
s.state.Payload.ProposalPayload = &privateParticipantsList*/
return internal.MachineCombinedResponse{
Response: responses.ProposalParticipantInvitationsResponse{},
Payload: &payload,
}, nil
}
//
func (s *SignatureProposalFSM) actionConfirmProposalByParticipant(event string, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionConfirmProposalByParticipant")
return
}
func (s *SignatureProposalFSM) actionDeclineProposalByParticipant(event string, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionDeclineProposalByParticipant")
return
}
func (s *SignatureProposalFSM) actionValidateProposal(event string, args ...interface{}) (response interface{}, err error) {
log.Println("I'm actionValidateProposal")
return
}

View File

@ -0,0 +1,58 @@
package signature_proposal_fsm
import (
"crypto/sha256"
"encoding/base64"
"github.com/p2p-org/dc4bc/fsm/state_machines/internal"
"github.com/p2p-org/dc4bc/fsm/types/responses"
"math/rand"
)
// Request and response mutators
func ProposalParticipantsQuorumToResponse(list *internal.ProposalConfirmationPrivateQuorum) responses.ProposalParticipantInvitationsResponse {
var response responses.ProposalParticipantInvitationsResponse
for quorumId, parcipant := range *list {
response = append(response, &responses.ProposalParticipantInvitationEntryResponse{
Title: parcipant.Title,
PubKeyFingerprint: quorumId,
// TODO: Add encryption
EncryptedInvitation: parcipant.InvitationSecret,
})
}
return response
}
// Common functions
func createFingerprint(data *[]byte) string {
hash := sha256.Sum256(*data)
return base64.StdEncoding.EncodeToString(hash[:])
}
// https://blog.questionable.services/article/generating-secure-random-numbers-crypto-rand/
// GenerateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func generateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
// Note that err == nil only if we read len(b) bytes.
if err != nil {
return nil, err
}
return b, nil
}
// GenerateRandomString returns a URL-safe, base64 encoded
// securely generated random string.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func generateRandomString(s int) (string, error) {
b, err := generateRandomBytes(s)
return base64.URLEncoding.EncodeToString(b), err
}

View File

@ -0,0 +1,68 @@
package signature_proposal_fsm
import (
"github.com/p2p-org/dc4bc/fsm/fsm"
"github.com/p2p-org/dc4bc/fsm/fsm_pool"
)
const (
fsmName = "signature_proposal_fsm"
signingIdLen = 32
stateAwaitProposalConfirmation = "validate_proposal" // waiting participants
stateValidationCanceledByParticipant = "validation_canceled_by_participant"
stateValidationCanceledByTimeout = "validation_canceled_by_timeout"
stateProposed = "proposed"
eventInitProposal = "proposal_init"
eventConfirmProposal = "proposal_confirm_by_participant"
eventDeclineProposal = "proposal_decline_by_participant"
eventValidateProposal = "proposal_validate"
eventSetProposalValidated = "proposal_set_validated"
eventSetValidationCanceledByTimeout = "proposal_canceled_timeout"
eventSwitchProposedToSigning = "switch_state_to_signing"
)
type SignatureProposalFSM struct {
*fsm.FSM
}
func New() fsm_pool.IStateMachine {
machine := &SignatureProposalFSM{}
machine.FSM = fsm.MustNewFSM(
fsmName,
fsm.StateGlobalIdle,
[]fsm.EventDesc{
// {Name: "", SrcState: []string{""}, DstState: ""},
// Init
{Name: eventInitProposal, SrcState: []string{fsm.StateGlobalIdle}, DstState: stateAwaitProposalConfirmation},
// Validate by participants
{Name: eventConfirmProposal, SrcState: []string{stateAwaitProposalConfirmation}, DstState: stateAwaitProposalConfirmation},
// Is decline event should auto change state to default, or it process will initiated by client (external emit)?
// Now set for external emitting.
{Name: eventDeclineProposal, SrcState: []string{stateAwaitProposalConfirmation}, DstState: stateValidationCanceledByParticipant},
{Name: eventValidateProposal, SrcState: []string{stateAwaitProposalConfirmation}, DstState: stateAwaitProposalConfirmation},
// eventProposalValidate internal or from client?
// yay
// Exit point
{Name: eventSetProposalValidated, SrcState: []string{stateAwaitProposalConfirmation}, DstState: "process_sig", IsInternal: true},
// nan
{Name: eventSetValidationCanceledByTimeout, SrcState: []string{stateAwaitProposalConfirmation}, DstState: stateValidationCanceledByTimeout, IsInternal: true},
},
fsm.Callbacks{
eventInitProposal: machine.actionInitProposal,
eventConfirmProposal: machine.actionConfirmProposalByParticipant,
eventDeclineProposal: machine.actionDeclineProposalByParticipant,
eventValidateProposal: machine.actionValidateProposal,
},
)
return machine
}

1
go.mod
View File

@ -3,6 +3,7 @@ module p2p.org/dc4bc
go 1.13
require (
github.com/looplab/fsm v0.1.0
github.com/makiuchi-d/gozxing v0.0.0-20190830103442-eaff64b1ceb7
github.com/mattn/go-gtk v0.0.0-20191030024613-af2e013261f5
github.com/mattn/go-pointer v0.0.0-20190911064623-a0a44394634f // indirect