github.com/consensys/gnark-crypto@v0.14.0/fiat-shamir/transcript.go (about)

     1  // Copyright 2020 ConsenSys Software Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package fiatshamir
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"hash"
    21  )
    22  
    23  // errChallengeNotFound is returned when a wrong challenge name is provided.
    24  var (
    25  	errChallengeNotFound            = errors.New("challenge not recorded in the transcript")
    26  	errChallengeAlreadyComputed     = errors.New("challenge already computed, cannot be binded to other values")
    27  	errPreviousChallengeNotComputed = errors.New("the previous challenge is needed and has not been computed")
    28  )
    29  
    30  // Transcript handles the creation of challenges for Fiat Shamir.
    31  type Transcript struct {
    32  	// hash function that is used.
    33  	h hash.Hash
    34  
    35  	challenges map[string]challenge
    36  	previous   *challenge
    37  }
    38  
    39  type challenge struct {
    40  	position   int      // position of the challenge in the Transcript. order matters.
    41  	bindings   [][]byte // bindings stores the variables a challenge is binded to.
    42  	value      []byte   // value stores the computed challenge
    43  	isComputed bool
    44  }
    45  
    46  // NewTranscript returns a new transcript.
    47  // h is the hash function that is used to compute the challenges.
    48  // challenges are the name of the challenges. The order of the challenges IDs matters.
    49  func NewTranscript(h hash.Hash, challengesID ...string) *Transcript {
    50  	challenges := make(map[string]challenge)
    51  	for i := range challengesID {
    52  		challenges[challengesID[i]] = challenge{position: i}
    53  	}
    54  	t := &Transcript{
    55  		challenges: challenges,
    56  		h:          h,
    57  	}
    58  	return t
    59  }
    60  
    61  // Bind binds the challenge to value. A challenge can be binded to an
    62  // arbitrary number of values, but the order in which the binded values
    63  // are added is important. Once a challenge is computed, it cannot be
    64  // binded to other values.
    65  func (t *Transcript) Bind(challengeID string, bValue []byte) error {
    66  
    67  	currentChallenge, ok := t.challenges[challengeID]
    68  	if !ok {
    69  		return errChallengeNotFound
    70  	}
    71  
    72  	if currentChallenge.isComputed {
    73  		return errChallengeAlreadyComputed
    74  	}
    75  
    76  	bCopy := make([]byte, len(bValue))
    77  	copy(bCopy, bValue)
    78  	currentChallenge.bindings = append(currentChallenge.bindings, bCopy)
    79  	t.challenges[challengeID] = currentChallenge
    80  
    81  	return nil
    82  
    83  }
    84  
    85  // ComputeChallenge computes the challenge corresponding to the given name.
    86  // The challenge is:
    87  // * H(name || previous_challenge || binded_values...) if the challenge is not the first one
    88  // * H(name || binded_values... ) if it is the first challenge
    89  func (t *Transcript) ComputeChallenge(challengeID string) ([]byte, error) {
    90  
    91  	challenge, ok := t.challenges[challengeID]
    92  	if !ok {
    93  		return nil, errChallengeNotFound
    94  	}
    95  
    96  	// if the challenge was already computed we return it
    97  	if challenge.isComputed {
    98  		return challenge.value, nil
    99  	}
   100  
   101  	// reset before populating the internal state
   102  	t.h.Reset()
   103  	defer t.h.Reset()
   104  
   105  	if _, err := t.h.Write([]byte(challengeID)); err != nil {
   106  		return nil, fmt.Errorf("write: %w", err)
   107  	}
   108  
   109  	// write the previous challenge if it's not the first challenge
   110  	if challenge.position != 0 {
   111  		if t.previous == nil || (t.previous.position != challenge.position-1) {
   112  			return nil, errPreviousChallengeNotComputed
   113  		}
   114  		if _, err := t.h.Write(t.previous.value[:]); err != nil {
   115  			return nil, err
   116  		}
   117  	}
   118  
   119  	// write the binded values in the order they were added
   120  	for _, b := range challenge.bindings {
   121  		if _, err := t.h.Write(b); err != nil {
   122  			return nil, err
   123  		}
   124  	}
   125  
   126  	// compute the hash of the accumulated values
   127  	res := t.h.Sum(nil)
   128  
   129  	challenge.value = make([]byte, len(res))
   130  	copy(challenge.value, res)
   131  	challenge.isComputed = true
   132  
   133  	t.challenges[challengeID] = challenge
   134  	t.previous = &challenge
   135  
   136  	return res, nil
   137  
   138  }