diff --git a/.github/.golangci.yml b/.github/.golangci.yml index 69261ab..03900c6 100644 --- a/.github/.golangci.yml +++ b/.github/.golangci.yml @@ -8,7 +8,7 @@ linters: - contextcheck - cyclop - deadcode - - depguard + #- depguard - dogsled - dupl - durationcheck @@ -199,5 +199,5 @@ issues: run: tests: false -#output: -# format: github-actions \ No newline at end of file +output: + format: github-actions \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62f5c67..068bd26 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,11 @@ name: FROST on: - push: - branches: [ main ] pull_request: - # The branches below must be a subset of the branches above - branches: [ main ] + branches: + - main + +permissions: + contents: read jobs: lint: @@ -12,17 +13,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@master with: fetch-depth: 0 - name: Setup Go - uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3 + uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@master with: go-version-file: ./go.mod # Lint - name: Linting - uses: golangci/golangci-lint-action@5c56cd6c9dc07901af25baab6f2b0d9f3b7c3018 # pin@5c56cd6c9dc07901af25baab6f2b0d9f3b7c3018 + uses: golangci/golangci-lint-action@5c56cd6c9dc07901af25baab6f2b0d9f3b7c3018 # pin@master with: version: latest args: --config=./.github/.golangci.yml ./... @@ -34,14 +35,14 @@ jobs: strategy: fail-fast: false matrix: - go: [ '1.20', '1.19' ] + go: [ '1.21' ] steps: - name: Checkout repo - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@master with: fetch-depth: 0 - name: Setup Go - uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3 + uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@master with: go-version: ${{ matrix.go }} @@ -51,15 +52,14 @@ jobs: analyze: name: Analyze - if: github.event_name == 'push' runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@master with: fetch-depth: 0 - name: Setup Go - uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@v3 + uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@master with: go-version-file: ./go.mod @@ -69,13 +69,13 @@ jobs: # Codecov - name: Codecov - uses: codecov/codecov-action@29386c70ef20e286228c72b668a06fd0e8399192 # pin@v1 + uses: codecov/codecov-action@29386c70ef20e286228c72b668a06fd0e8399192 # pin@master with: file: .github/coverage.out # Sonar - name: SonarCloud Scan - uses: SonarSource/sonarcloud-github-action@cb201f3b2d7a38231a8c042dfea4539c8bea180b # pin@master + uses: SonarSource/sonarcloud-github-action@5ee47de3c96f0c1c51b09d2ff1fec0cfeefcf67c # pin@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 494b417..1efceab 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,16 +24,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@dc323e67f16fb5f7663d20ff7941f27f5809e9b6 # pin@v2 + uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # pin@master # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@231aa2c8a89117b126725a0e11897209b7118144 # pin@v1 + uses: github/codeql-action/init@231aa2c8a89117b126725a0e11897209b7118144 # pin@master with: languages: go - name: Autobuild - uses: github/codeql-action/autobuild@231aa2c8a89117b126725a0e11897209b7118144 # pin@v1 + uses: github/codeql-action/autobuild@231aa2c8a89117b126725a0e11897209b7118144 # pin@master - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@231aa2c8a89117b126725a0e11897209b7118144 # pin@v1 + uses: github/codeql-action/analyze@231aa2c8a89117b126725a0e11897209b7118144 # pin@master diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index fd52c4a..239e26e 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -22,12 +22,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0 + uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # pin@master with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@c1aec4ac820532bab364f02a81873c555a0ba3a1 # v1.0.4 + uses: ossf/scorecard-action@c1aec4ac820532bab364f02a81873c555a0ba3a1 # pin@master with: results_file: results.sarif results_format: sarif @@ -42,7 +42,7 @@ jobs: # Upload the results as artifacts (optional). - name: "Upload artifact" - uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2.3.1 + uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # pin@master with: name: SARIF file path: results.sarif @@ -50,6 +50,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # v1.0.26 + uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # pin@master with: sarif_file: results.sarif diff --git a/commitment.go b/commitment.go new file mode 100644 index 0000000..0d16bb8 --- /dev/null +++ b/commitment.go @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + +package frost + +import ( + "errors" + "fmt" + "slices" + + group "github.com/bytemare/crypto" + + "github.com/bytemare/frost/internal" +) + +var errDecodeCommitmentLength = errors.New("failed to decode commitment: invalid length") + +// Commitment is a participant's one-time commitment holding its identifier, and hiding and binding nonces. +type Commitment struct { + Identifier *group.Scalar + HidingNonce *group.Element + BindingNonce *group.Element +} + +// Encode returns the serialized byte encoding of a participant's commitment. +func (c Commitment) Encode() []byte { + id := c.Identifier.Encode() + hNonce := c.HidingNonce.Encode() + bNonce := c.BindingNonce.Encode() + + out := make([]byte, len(id)+len(hNonce)+len(bNonce)) + copy(out, id) + copy(out[len(id):], hNonce) + copy(out[len(id)+len(hNonce):], bNonce) + + return out +} + +// DecodeCommitment attempts to deserialize the encoded commitment given as input, and to return it. +func DecodeCommitment(cs Ciphersuite, data []byte) (*Commitment, error) { + g := cs.Configuration().Ciphersuite.Group + scalarLength := g.ScalarLength() + elementLength := g.ElementLength() + + if len(data) != scalarLength+2*elementLength { + return nil, errDecodeCommitmentLength + } + + c := &Commitment{ + Identifier: g.NewScalar(), + HidingNonce: g.NewElement(), + BindingNonce: g.NewElement(), + } + + if err := c.Identifier.Decode(data[:scalarLength]); err != nil { + return nil, fmt.Errorf("failed to decode commitment identifier: %w", err) + } + + if err := c.HidingNonce.Decode(data[:scalarLength]); err != nil { + return nil, fmt.Errorf("failed to decode commitment hiding nonce: %w", err) + } + + if err := c.BindingNonce.Decode(data[:scalarLength]); err != nil { + return nil, fmt.Errorf("failed to decode commitment binding nonce: %w", err) + } + + return c, nil +} + +// CommitmentList is a sortable list of commitments. +type CommitmentList []*Commitment + +func cmpID(a, b *Commitment) int { + switch { + case a.Identifier.Equal(b.Identifier) == 1: // a == b + return 0 + case a.Identifier.LessOrEqual(b.Identifier) == 1: // a < b + return -1 + default: + return 1 + } +} + +// Sort sorts the list the ascending order of identifiers. +func (c CommitmentList) Sort() { + slices.SortFunc(c, cmpID) +} + +// IsSorted returns whether the list is sorted in ascending order by identifier. +func (c CommitmentList) IsSorted() bool { + return slices.IsSortedFunc(c, cmpID) +} + +// Encode serializes a whole commitment list. +func (c CommitmentList) Encode() []byte { + var encoded []byte + + for _, l := range c { + e := internal.Concatenate(l.Identifier.Encode(), l.HidingNonce.Encode(), l.BindingNonce.Encode()) + encoded = append(encoded, e...) + } + + return encoded +} + +// Participants returns the list of participants in the commitment list. +func (c CommitmentList) Participants() []*group.Scalar { + identifiers := make([]*group.Scalar, len(c)) + for i, l := range c { + identifiers[i] = l.Identifier + } + + return identifiers +} + +// Get returns the commitment of the participant with the corresponding identifier, or nil if it was not found. +func (c CommitmentList) Get(identifier *group.Scalar) *Commitment { + for _, com := range c { + if com.Identifier.Equal(identifier) == 1 { + return com + } + } + + return nil +} diff --git a/coordinator.go b/coordinator.go index 643da58..06f4260 100644 --- a/coordinator.go +++ b/coordinator.go @@ -11,14 +11,11 @@ package frost import ( group "github.com/bytemare/crypto" secretsharing "github.com/bytemare/secret-sharing" - - "github.com/bytemare/frost/internal" - "github.com/bytemare/frost/internal/schnorr" ) // Aggregate allows the coordinator to produce the final signature given all signature shares. // -// Before aggregation, each signature share must be a valid deserialized element. If that validation fails the +// Before aggregation, each signature share must be a valid, deserialized element. If that validation fails the // coordinator must abort the protocol, as the resulting signature will be invalid. // The CommitmentList must be sorted in ascending order by identifier. // @@ -26,27 +23,27 @@ import ( // This aggregate signature will verify if and only if all signature shares are valid. If an invalid share is identified // a reasonable approach is to remove the participant from the set of allowed participants in future runs of FROST. func (p *Participant) Aggregate( - list internal.CommitmentList, + list CommitmentList, msg []byte, - sigShares []*group.Scalar, -) *schnorr.Signature { + sigShares []*SignatureShare, +) *Signature { if !list.IsSorted() { panic("list not sorted") } // Compute binding factors - bindingFactorList, _ := list.ComputeBindingFactors(p.Ciphersuite, msg) + bindingFactorList := p.computeBindingFactors(list, msg) // Compute group commitment - groupCommitment := list.ComputeGroupCommitment(p.Ciphersuite, bindingFactorList) + groupCommitment := p.computeGroupCommitment(list, bindingFactorList) // Compute aggregate signature - z := p.Ciphersuite.Group.NewScalar().Zero() - for _, zi := range sigShares { - z.Add(zi) + z := p.Ciphersuite.Group.NewScalar() + for _, share := range sigShares { + z.Add(share.SignatureShare) } - return &schnorr.Signature{ + return &Signature{ R: groupCommitment, Z: z, } @@ -58,11 +55,10 @@ func (p *Participant) Aggregate( // // The CommitmentList must be sorted in ascending order by identifier. func (p *Participant) VerifySignatureShare( - id *group.Scalar, + commitment *Commitment, pki *group.Element, - commi [2]*group.Element, sigShareI *group.Scalar, - coms internal.CommitmentList, + coms CommitmentList, msg []byte, ) bool { if !coms.IsSorted() { @@ -70,22 +66,22 @@ func (p *Participant) VerifySignatureShare( } // Compute Binding Factor(s) - bindingFactorList, _ := coms.ComputeBindingFactors(p.Ciphersuite, msg) - bindingFactor := bindingFactorList.BindingFactorForParticipant(id) + bindingFactorList := p.computeBindingFactors(coms, msg) + bindingFactor := bindingFactorList.BindingFactorForParticipant(commitment.Identifier) // Compute Group Commitment - groupCommitment := coms.ComputeGroupCommitment(p.Ciphersuite, bindingFactorList) + groupCommitment := p.computeGroupCommitment(coms, bindingFactorList) // Commitment KeyShare - commShare := commi[0].Copy().Add(commi[1].Copy().Multiply(bindingFactor)) + commShare := commitment.HidingNonce.Copy().Add(commitment.BindingNonce.Copy().Multiply(bindingFactor)) // Compute the challenge - challenge := schnorr.Challenge(p.Ciphersuite, groupCommitment, p.Configuration.GroupPublicKey, msg) + challenge := challenge(p.Ciphersuite, groupCommitment, p.Configuration.GroupPublicKey, msg) // Compute the interpolating value participantList := secretsharing.Polynomial(coms.Participants()) - lambdaI, err := participantList.DeriveInterpolatingValue(p.Ciphersuite.Group, id) + lambdaI, err := participantList.DeriveInterpolatingValue(p.Ciphersuite.Group, commitment.Identifier) if err != nil { panic(err) } diff --git a/dkg/dkg.go b/dkg/dkg.go index 8b38c54..a9e64cb 100644 --- a/dkg/dkg.go +++ b/dkg/dkg.go @@ -18,8 +18,8 @@ import ( group "github.com/bytemare/crypto" secretsharing "github.com/bytemare/secret-sharing" + "github.com/bytemare/frost" "github.com/bytemare/frost/internal" - "github.com/bytemare/frost/internal/schnorr" ) var ( @@ -35,14 +35,15 @@ var ( // Round1Data is the output data of the Init() function, to be broadcast to all participants. type Round1Data struct { - ProofOfKnowledge schnorr.Signature + ProofOfKnowledge frost.Signature SenderIdentifier *group.Scalar Commitment []*group.Element } // Round2Data is an output of the Continue() function, to be sent to the Receiver. type Round2Data struct { - SenderIdentifier *group.Scalar + SenderIdentifier *group.Scalar + ReceiverIdentifier *group.Scalar SecretShare *group.Scalar } @@ -54,14 +55,14 @@ type Participant struct { secretShare *group.Scalar coefficients secretsharing.Polynomial ciphersuite internal.Ciphersuite - maxSigner int + maxSigners int threshold int } // NewParticipant instantiates a new participant with identifier id. -func NewParticipant(c internal.Ciphersuite, id *group.Scalar, maxSigner, threshold int) *Participant { +func NewParticipant(c internal.Ciphersuite, id *group.Scalar, maxSigners, threshold int) *Participant { return &Participant{ - maxSigner: maxSigner, + maxSigners: maxSigners, threshold: threshold, ciphersuite: c, Identifier: id, @@ -100,7 +101,7 @@ func (p *Participant) Init() *Round1Data { package1 := &Round1Data{ SenderIdentifier: p.Identifier, Commitment: com, - ProofOfKnowledge: schnorr.Signature{ + ProofOfKnowledge: frost.Signature{ R: r, Z: mu, }, @@ -112,13 +113,17 @@ func (p *Participant) Init() *Round1Data { // Continue ingests the broadcast data from other peers and returns a dedicated Round2Data structure for each peer. func (p *Participant) Continue(r1data []*Round1Data) ([]*Round2Data, error) { - if len(r1data) != p.maxSigner { + if len(r1data) != p.maxSigners { return nil, errRound1DataElements } r2data := make([]*Round2Data, 0, len(r1data)-1) for _, r1package := range r1data { + if r1package == nil { + continue + } + peer := r1package.SenderIdentifier if peer.Equal(p.Identifier) == 1 { continue @@ -158,7 +163,7 @@ func (p *Participant) Finalize( r1data []*Round1Data, r2data []*Round2Data, ) (signingShare *group.Scalar, verificationShare, groupPublic *group.Element, err error) { - if len(r1data) != p.maxSigner { + if len(r1data) != p.maxSigners { return nil, nil, nil, errRound1DataElements } diff --git a/examples_test.go b/examples_test.go new file mode 100644 index 0000000..b3b2807 --- /dev/null +++ b/examples_test.go @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree or at +// https://spdx.org/licenses/MIT.html + +package frost_test + +import ( + "encoding/hex" + "fmt" + + group "github.com/bytemare/crypto" + + "github.com/bytemare/frost" + "github.com/bytemare/frost/dkg" +) + +var ( + participantGeneratedInDKG *frost.Participant + commitment *frost.Commitment + groupPublicKeyGeneratedInDKG *group.Element +) + +// Example_dkg shows the distributed key generation procedure that must be executed by each participant to build the secret key. +func Example_dkg() { + // Each participant must be set to use the same configuration. + maximumAmountOfParticipants := 1 + threshold := 1 + configuration := frost.Ristretto255.Configuration() + + // Step 1: Initialise your participant. Each participant must be given an identifier that MUST be unique among + // all participants. For this example, this participant will have id = 1. + participantIdentifier := configuration.IDFromInt(1) + dkgParticipant := dkg.NewParticipant( + configuration.Ciphersuite, + participantIdentifier, + maximumAmountOfParticipants, + threshold, + ) + + // Step 2: Call Init() on each participant. This will return data that must be broadcast to all other participants + // over a secure channel. + round1Data := dkgParticipant.Init() + if round1Data.SenderIdentifier.Equal(participantIdentifier) != 1 { + panic("this is just a test, and it failed") + } + + // Step 3: First, collect all round1Data from all other participants. Then call Continue() on each participant + // providing them with the compiled data. + accumulatedRound1Data := make([]*dkg.Round1Data, 0, maximumAmountOfParticipants) + accumulatedRound1Data = append(accumulatedRound1Data, round1Data) + + // This will return a dedicated package for each other participant that must be sent to them over a secure channel. + // The intended receiver is specified in the returned data. + // We ignore the error for the demo, but execution MUST be aborted upon errors. + round2Data, err := dkgParticipant.Continue(accumulatedRound1Data) + if err != nil { + panic(err) + } else if len(round2Data) != len(accumulatedRound1Data)-1 { + panic("this is just a test, and it failed") + } + + // Step 3: First, collect all round2Data from all other participants. Then call Finalize() on each participant + // providing the same input as for Continue() and the collected data from the second round2. + accumulatedRound2Data := round2Data + + // This will, for each participant, return their secret key (which is a share of the global secret signing key), + // the corresponding verification key, and the global public key. + // We ignore the error for the demo, but execution MUST be aborted upon errors. + var participantsSecretKey *group.Scalar + participantsSecretKey, _, groupPublicKeyGeneratedInDKG, err = dkgParticipant.Finalize( + accumulatedRound1Data, + accumulatedRound2Data, + ) + if err != nil { + panic(err) + } + + // It is important to set the group's public key. + configuration.GroupPublicKey = groupPublicKeyGeneratedInDKG + + // Now you can build a Signing Participant for the FROST protocol with this ID and key. + participantGeneratedInDKG = configuration.Participant(participantIdentifier, participantsSecretKey) + + fmt.Printf("Signing keys for participant set up. ID: %s\n", hex.EncodeToString(participantIdentifier.Encode())) + + // Output: Signing keys for participant set up. ID: 0100000000000000000000000000000000000000000000000000000000000000 +} + +// Example_signer shows the execution steps of a FROST participant. +func Example_signer() { + // The following are your setup variables and configuration. + numberOfParticipants := 1 + + // We assume you already have a pool of participants with distinct non-zero identifiers and their signing share. + // See Example_dkg() on how to do generate these shares. + Example_dkg() + participant := participantGeneratedInDKG + + // Step 1: call Commit() on each participant. This will return the participant's single-use commitment. + // Send this to the coordinator or all other participants over an authenticated + // channel (confidentiality is not required). + // A participant keeps an internal state during the protocol run across the two rounds. + commitment = participant.Commit() + if commitment.Identifier.Equal(participant.KeyShare.Identifier) != 1 { + panic("this is just a test and it failed") + } + + // Step 2: collect the commitments from the other participants and coordinator-chosen the message to sign, + // and finalize by signing the message. + message := []byte("example") + commitments := make(frost.CommitmentList, 0, numberOfParticipants) + commitments = append(commitments, commitment) + + // This will produce a signature share to be sent back to the coordinator. + // We ignore the error for the demo, but execution MUST be aborted upon errors. + signatureShare, _ := participant.Sign(message, commitments) + if !participant.VerifySignatureShare( + commitment, + participant.GroupPublicKey, + signatureShare.SignatureShare, + commitments, + message, + ) { + panic("this is a test and it failed") + } + + fmt.Println("Signing successful.") + + // Output: Signing keys for participant set up. ID: 0100000000000000000000000000000000000000000000000000000000000000 + // Signing successful. +} + +// Example_coordinator shows the execution steps of a FROST coordinator. +func Example_coordinator() { + /* + The Coordinator is an entity with the following responsibilities: + + 1. Determining which participants will participate (at least MIN_PARTICIPANTS in number); + 2. Coordinating rounds (receiving and forwarding inputs among participants); and + 3. Aggregating signature shares output by each participant, and publishing the resulting signature. + + Note that it is possible to deploy the protocol without a distinguished Coordinator. + */ + + // The following are your setup variables and configuration. + const ( + maxParticipants = 1 + numberOfParticipants = 1 // Must be >= to the threshold, and <= to the total number of participants. + ) + + // 0. We suppose a previous run of DKG with a setup of participants. Here we will only use 1 participant. + Example_dkg() + participant := participantGeneratedInDKG + groupPublicKey := groupPublicKeyGeneratedInDKG + + // A coordinator CAN be a participant. In this instance, we chose it not to be one. + configuration := frost.Ristretto255.Configuration(groupPublicKey) + coordinator := configuration.Participant(nil, nil) + + // 1. Determine which participants will participate (at least MIN_PARTICIPANTS in number). + //participantIdentifiers := [numberOfParticipants]*group.Scalar{ + // participant.KeyShare.Identifier, + //} + participantPublicKeys := []*group.Element{ + participant.ParticipantInfo.PublicKey, + } + + // 2. Receive the participant's commitments and sort the list. Then send the message to be signed and the sorted + // received commitment list to each participant. + commitments := frost.CommitmentList{ + participant.Commit(), + } + message := []byte("example") + + commitments.Sort() + + // 3. Collect the participants signature shares, and aggregate them to produce the final signature. This signature + // SHOULD be verified. + p1SignatureShare, _ := participant.Sign(message, commitments) + signatureShares := [numberOfParticipants]*frost.SignatureShare{ + p1SignatureShare, + } + + signature := coordinator.Aggregate(commitments, message, signatureShares[:]) + + if !frost.Verify(configuration.Ciphersuite, message, signature, groupPublicKey) { + fmt.Println("invalid signature") + // At this point one should try to identify which participant's signature share is invalid and act on it. + // This verification is done as follows: + for i, signatureShare := range signatureShares { + // Verify whether we have the participants commitment + commitmentI := commitments.Get(signatureShare.Identifier) + if commitmentI == nil { + panic("commitment not found") + } + + // Get the public key corresponding to the signature share's participant + pki := participantPublicKeys[i-1] + + if !coordinator.VerifySignatureShare( + commitmentI, + pki, + signatureShare.SignatureShare, + commitments, + message, + ) { + fmt.Printf("participant %v produced an invalid signature share", signatureShare.Identifier.Encode()) + } + } + + panic("Failed.") + } + + fmt.Printf("Valid signature for %q.", message) + + // Output: Signing keys for participant set up. ID: 0100000000000000000000000000000000000000000000000000000000000000 + // Valid signature for "example". +} diff --git a/frost.go b/frost.go index e69396c..ad4b635 100644 --- a/frost.go +++ b/frost.go @@ -36,14 +36,14 @@ const ( // Secp256k1 uses Secp256k1 and SHA-256. Secp256k1 - ed25519ContextString = "FROST-ED25519-SHA512-v11" - ristretto255ContextString = "FROST-RISTRETTO255-SHA512-v11" - p256ContextString = "FROST-P256-SHA256-v11" - secp256k1ContextString = "FROST-secp256k1-SHA256-v11" + ed25519ContextString = "FROST-ED25519-SHA512-v1" + ristretto255ContextString = "FROST-RISTRETTO255-SHA512-v1" + p256ContextString = "FROST-P256-SHA256-v1" + secp256k1ContextString = "FROST-secp256k1-SHA256-v1" /* - ed448ContextString = "FROST-ED448-SHAKE256-v11" + ed448ContextString = "FROST-ED448-SHAKE256-v1" */ ) @@ -60,15 +60,20 @@ func (c Ciphersuite) Available() bool { } // Configuration returns a configuration created for the ciphersuite. -func (c Ciphersuite) Configuration() *Configuration { +func (c Ciphersuite) Configuration(groupPublicKey ...*group.Element) *Configuration { if !c.Available() { return nil } + var pk *group.Element + if len(groupPublicKey) != 0 { + pk = groupPublicKey[0] + } + switch c { case Ed25519: return &Configuration{ - GroupPublicKey: nil, + GroupPublicKey: pk, Ciphersuite: internal.Ciphersuite{ ContextString: []byte(ed25519ContextString), Hash: hash.SHA512, @@ -77,7 +82,7 @@ func (c Ciphersuite) Configuration() *Configuration { } case Ristretto255: return &Configuration{ - GroupPublicKey: nil, + GroupPublicKey: pk, Ciphersuite: internal.Ciphersuite{ Group: group.Ristretto255Sha512, Hash: hash.SHA512, @@ -86,7 +91,7 @@ func (c Ciphersuite) Configuration() *Configuration { } case P256: return &Configuration{ - GroupPublicKey: nil, + GroupPublicKey: pk, Ciphersuite: internal.Ciphersuite{ Group: group.P256Sha256, Hash: hash.SHA256, @@ -95,7 +100,7 @@ func (c Ciphersuite) Configuration() *Configuration { } case Secp256k1: return &Configuration{ - GroupPublicKey: nil, + GroupPublicKey: pk, Ciphersuite: internal.Ciphersuite{ ContextString: []byte(secp256k1ContextString), Hash: hash.SHA256, @@ -115,6 +120,22 @@ type Configuration struct { Ciphersuite internal.Ciphersuite } +// IDFromInt returns a valid ID from and integer given the configuration. +func (c Configuration) IDFromInt(id int) *group.Scalar { + s := c.Ciphersuite.Group.NewScalar() + + switch id { + case 0: + s.Zero() + case 1: + s.One() + default: + s = internal.IntegerToScalar(c.Ciphersuite.Group, id) + } + + return s +} + // Participant returns a new participant of the protocol instantiated from the configuration an input. func (c Configuration) Participant(id, keyShare *group.Scalar) *Participant { return &Participant{ @@ -123,7 +144,8 @@ func (c Configuration) Participant(id, keyShare *group.Scalar) *Participant { Identifier: id, SecretKey: keyShare, }, - Lambda: nil, + Lambda: nil, + PublicKey: c.Ciphersuite.Group.Base().Multiply(keyShare), }, Nonce: [2]*group.Scalar{}, HidingRandom: nil, @@ -132,13 +154,13 @@ func (c Configuration) Participant(id, keyShare *group.Scalar) *Participant { } } -// Commitment is the tuple defining a commitment. -type Commitment []*group.Element +// PublicKeys is the tuple defining a commitment. +type PublicKeys []*group.Element // DeriveGroupInfo returns the group public key as well those from all participants. -func DeriveGroupInfo(g group.Group, max int, coms secretsharing.Commitment) (*group.Element, Commitment) { +func DeriveGroupInfo(g group.Group, max int, coms secretsharing.Commitment) (*group.Element, PublicKeys) { pk := coms[0] - keys := make(Commitment, max) + keys := make(PublicKeys, max) for i := 0; i < max; i++ { id := internal.IntegerToScalar(g, i) @@ -186,8 +208,8 @@ func derivePublicPoint(g group.Group, coms secretsharing.Commitment, i *group.Sc return publicPoint } -// Verify allows verification of a participant's secret share given a VSS commitment to the secret polynomial. -func Verify(g group.Group, share *secretsharing.KeyShare, coms secretsharing.Commitment) bool { +// VerifyVSS allows verification of a participant's secret share given a VSS commitment to the secret polynomial. +func VerifyVSS(g group.Group, share *secretsharing.KeyShare, coms secretsharing.Commitment) bool { pk := g.Base().Multiply(share.SecretKey) return secretsharing.Verify(g, share.Identifier, pk, coms) } diff --git a/go.mod b/go.mod index 9cf8807..4580564 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/bytemare/frost -go 1.20 +go 1.21 require ( filippo.io/edwards25519 v1.0.0 - github.com/bytemare/crypto v0.5.1 + github.com/bytemare/crypto v0.5.2 github.com/bytemare/hash v0.1.5 github.com/bytemare/secret-sharing v0.1.0 github.com/gtank/ristretto255 v0.1.2 @@ -12,7 +12,8 @@ require ( require ( filippo.io/nistec v0.0.2 // indirect - github.com/bytemare/hash2curve v0.1.3 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/sys v0.6.0 // indirect + github.com/bytemare/hash2curve v0.2.2 // indirect + github.com/bytemare/secp256k1 v0.1.0 // indirect + golang.org/x/crypto v0.8.0 // indirect + golang.org/x/sys v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 974e7d8..696a2d3 100644 --- a/go.sum +++ b/go.sum @@ -2,17 +2,19 @@ filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= filippo.io/nistec v0.0.2 h1:/NIXTUimcHIh0E2DsYucHlICvUisgj28/XEnKSEptUs= filippo.io/nistec v0.0.2/go.mod h1:84fxC9mi+MhC2AERXI4LSa8cmSVOzrFikg6hZ4IfCyw= -github.com/bytemare/crypto v0.5.1 h1:JxtwOx0ZhF/0Tyqg1njQwRcqtiTNnISx2z2DRn9lAbs= -github.com/bytemare/crypto v0.5.1/go.mod h1:UA6K3SBPZ0C2VHQXc/9LT93rWTBwXxXNZFNL4uwapPo= +github.com/bytemare/crypto v0.5.2 h1:ogvfY5mmtrPc5Uhwq4mUEUDnTVig+UEF8gwnNAPaNbU= +github.com/bytemare/crypto v0.5.2/go.mod h1:kkx4ciRQFWcjMauezZo9SHw4YmqSTolWkfOVVTOXgAY= github.com/bytemare/hash v0.1.5 h1:VW+X1YQ2b3chjRFHkRUnO42uclsQjXimdBCPOgIobR4= github.com/bytemare/hash v0.1.5/go.mod h1:+QmWXTky/2b63ngqM5IYezGydn9UTFDhpX7mLYwYxCA= -github.com/bytemare/hash2curve v0.1.3 h1:BOqV8BF5dF+BbPZgIyoeAVTwd4m7jmw4LwacD1GFBvU= -github.com/bytemare/hash2curve v0.1.3/go.mod h1:Wma3DmJdn8kqiK9j120hkWvC3tQVKS1PyA8ZzyG23BI= +github.com/bytemare/hash2curve v0.2.2 h1:zaGx6Z4/N4Pl9B7aGNtpbZ09vu1NNJGoJRRtHHl8oTw= +github.com/bytemare/hash2curve v0.2.2/go.mod h1:Wma3DmJdn8kqiK9j120hkWvC3tQVKS1PyA8ZzyG23BI= +github.com/bytemare/secp256k1 v0.1.0 h1:kjVJ06GAHSa+EJ7Rz1LdVgE0DQWdvUT77tmcGf7epXQ= +github.com/bytemare/secp256k1 v0.1.0/go.mod h1:hzquMsr3GXhVcqL9qFX7GGjmcT5dlQldKrArd7tcXHE= github.com/bytemare/secret-sharing v0.1.0 h1:p+5TQw40/JnSMUgSe0OdvUACo9xZuiQDL/eLRPjPd50= github.com/bytemare/secret-sharing v0.1.0/go.mod h1:NKTAXEhsxrVi2Yr5BCKFP9uhPWDtBStAPbyqLghFG4I= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/commitment.go b/internal/commitment.go deleted file mode 100644 index 3be7d74..0000000 --- a/internal/commitment.go +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: MIT -// -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. -// -// This source code is licensed under the MIT license found in the -// LICENSE file in the root directory of this source tree or at -// https://spdx.org/licenses/MIT.html - -package internal - -import ( - "sort" - - group "github.com/bytemare/crypto" -) - -// Commitment represent a participant's commitment. -type Commitment struct { - ID *group.Scalar - HidingNonce *group.Element - BindingNonce *group.Element -} - -// CommitmentList is a sortable list of commitments. -type CommitmentList []*Commitment - -// Len implements the sort.Interface Len method. -func (c CommitmentList) Len() int { - return len(c) -} - -// Less implements the sort.Interface Less method. -func (c CommitmentList) Less(i, j int) bool { - return c[i].ID.LessOrEqual(c[j].ID) == 1 -} - -// Swap implements the sort.Interface Swap method. -func (c CommitmentList) Swap(i, j int) { - c[i], c[j] = c[j], c[i] -} - -// Sort sorts the list the ascending order of identifiers. -func (c CommitmentList) Sort() { - sort.Sort(c) -} - -// IsSorted returns whether the list is sorted in ascending order by identifier. -func (c CommitmentList) IsSorted() bool { - return sort.IsSorted(c) -} - -// Encode serializes a whole commitment list. -func (c CommitmentList) Encode() []byte { - var encoded []byte - - for _, l := range c { - e := Concatenate(l.ID.Encode(), l.HidingNonce.Encode(), l.BindingNonce.Encode()) - encoded = append(encoded, e...) - } - - return encoded -} - -// Participants returns the list of participants in the commitment list. -func (c CommitmentList) Participants() []*group.Scalar { - identifiers := make([]*group.Scalar, len(c)) - for i, l := range c { - identifiers[i] = l.ID - } - - return identifiers -} - -// ComputeBindingFactors computes binding factors based on the participant commitment list and the message to be signed. -// The rhoInputs are temporarily added for testing purposes and can be ignored. -func (c CommitmentList) ComputeBindingFactors(cs Ciphersuite, msg []byte) (l BindingFactorList, r [][]byte) { - if !c.IsSorted() { - panic(nil) - } - - h := cs.H4(msg) - encodedCommitHash := cs.H5(c.Encode()) - rhoInputPrefix := Concatenate(h, encodedCommitHash) - - bindingFactorList := make(BindingFactorList, len(c)) - rhoInputs := make([][]byte, len(c)) - - for i, l := range c { - rhoInput := Concatenate(rhoInputPrefix, l.ID.Encode()) - bindingFactor := cs.H1(rhoInput) - - bindingFactorList[i] = &BindingFactor{ - Identifier: l.ID, - BindingFactor: bindingFactor, - } - rhoInputs[i] = rhoInput - } - - return bindingFactorList, rhoInputs -} - -// ComputeGroupCommitment creates the group commitment from a commitment list. -func (c CommitmentList) ComputeGroupCommitment(cs Ciphersuite, list BindingFactorList) *group.Element { - if !c.IsSorted() { - panic(nil) - } - - gc := cs.Group.NewElement().Identity() - - for _, commitment := range c { - factor := list.BindingFactorForParticipant(commitment.ID) - gc.Add(commitment.HidingNonce).Add(commitment.BindingNonce.Copy().Multiply(factor)) - } - - return gc -} diff --git a/participant.go b/participant.go index 03bbb6f..d1851e1 100644 --- a/participant.go +++ b/participant.go @@ -9,13 +9,13 @@ package frost import ( + "errors" "fmt" group "github.com/bytemare/crypto" secretsharing "github.com/bytemare/secret-sharing" "github.com/bytemare/frost/internal" - "github.com/bytemare/frost/internal/schnorr" ) // Participant is a signer of a group. @@ -27,10 +27,13 @@ type Participant struct { Configuration } +var errDecodeSignatureShare = errors.New("failed to decode signature share: invalid length") + // ParticipantInfo holds the participant specific long-term values. type ParticipantInfo struct { - KeyShare *secretsharing.KeyShare - Lambda *group.Scalar // lamba can be computed once and reused across FROST signing operations + KeyShare *secretsharing.KeyShare + Lambda *group.Scalar // lamba can be computed once and reused across FROST signing operations + PublicKey *group.Element } func (p *Participant) generateNonce(s *group.Scalar, random []byte) *group.Scalar { @@ -80,18 +83,19 @@ func RecoverParticipant(c Ciphersuite, backup []byte) (*Participant, error) { p := conf.Participant(id, share) p.Lambda = lambda + p.PublicKey = conf.Ciphersuite.Group.Base().Multiply(share) return p, nil } // Commit generates a participants nonce and commitment, to be used in the second FROST round. The nonce must be kept // secret, and the commitment sent to the coordinator. -func (p *Participant) Commit() *internal.Commitment { +func (p *Participant) Commit() *Commitment { p.Nonce[0] = p.generateNonce(p.ParticipantInfo.KeyShare.SecretKey, p.HidingRandom) p.Nonce[1] = p.generateNonce(p.ParticipantInfo.KeyShare.SecretKey, p.BindingRandom) - return &internal.Commitment{ - ID: p.ParticipantInfo.KeyShare.Identifier.Copy(), + return &Commitment{ + Identifier: p.ParticipantInfo.KeyShare.Identifier.Copy(), HidingNonce: p.Ciphersuite.Group.Base().Multiply(p.Nonce[0]), BindingNonce: p.Ciphersuite.Group.Base().Multiply(p.Nonce[1]), } @@ -103,13 +107,13 @@ func (p *Participant) Commit() *internal.Commitment { // In particular, the Signer MUST validate commitment_list, deserializing each group Element in the list using // DeserializeElement from {{dep-pog}}. If deserialization fails, the Signer MUST abort the protocol. Moreover, // each participant MUST ensure that its identifier and commitments (from the first round) appear in commitment_list. -func (p *Participant) Sign(msg []byte, list internal.CommitmentList) (*group.Scalar, error) { +func (p *Participant) Sign(msg []byte, list CommitmentList) (*SignatureShare, error) { // Compute the binding factor(s) - bindingFactorList, _ := list.ComputeBindingFactors(p.Ciphersuite, msg) + bindingFactorList := p.computeBindingFactors(list, msg) bindingFactor := bindingFactorList.BindingFactorForParticipant(p.KeyShare.Identifier) // Compute group commitment - groupCommitment := list.ComputeGroupCommitment(p.Ciphersuite, bindingFactorList) + groupCommitment := p.computeGroupCommitment(list, bindingFactorList) // Compute the interpolating value participantList := secretsharing.Polynomial(list.Participants()) @@ -122,7 +126,7 @@ func (p *Participant) Sign(msg []byte, list internal.CommitmentList) (*group.Sca p.Lambda = lambdaID.Copy() // Compute per message challenge - challenge := schnorr.Challenge(p.Ciphersuite, groupCommitment, p.Configuration.GroupPublicKey, msg) + challenge := challenge(p.Ciphersuite, groupCommitment, p.Configuration.GroupPublicKey, msg) // Compute the signature share sigShare := p.Nonce[0].Add( @@ -133,5 +137,97 @@ func (p *Participant) Sign(msg []byte, list internal.CommitmentList) (*group.Sca p.Nonce[0].Zero() p.Nonce[1].Zero() - return sigShare, nil + return &SignatureShare{ + Identifier: p.ParticipantInfo.KeyShare.Identifier.Copy(), + SignatureShare: sigShare, + }, nil +} + +// computeBindingFactors computes binding factors based on the participant commitment list and the message to be signed. +func (p *Participant) computeBindingFactors(l CommitmentList, message []byte) internal.BindingFactorList { + if !l.IsSorted() { + panic(nil) + } + + h := p.Configuration.Ciphersuite.H4(message) + encodedCommitHash := p.Configuration.Ciphersuite.H5(l.Encode()) + rhoInputPrefix := internal.Concatenate(p.GroupPublicKey.Encode(), h, encodedCommitHash) + + bindingFactorList := make(internal.BindingFactorList, len(l)) + + for i, commitment := range l { + rhoInput := internal.Concatenate(rhoInputPrefix, commitment.Identifier.Encode()) + bindingFactor := p.Configuration.Ciphersuite.H1(rhoInput) + + bindingFactorList[i] = &internal.BindingFactor{ + Identifier: commitment.Identifier, + BindingFactor: bindingFactor, + } + } + + return bindingFactorList +} + +// computeGroupCommitment creates the group commitment from a commitment list. +func (p *Participant) computeGroupCommitment(l CommitmentList, list internal.BindingFactorList) *group.Element { + if !l.IsSorted() { + panic(nil) + } + + gc := p.Configuration.Ciphersuite.Group.NewElement().Identity() + + for _, commitment := range l { + if commitment.HidingNonce.IsIdentity() || commitment.BindingNonce.IsIdentity() { + panic("identity commitment") + } + + factor := list.BindingFactorForParticipant(commitment.Identifier) + bindingNonce := commitment.BindingNonce.Copy().Multiply(factor) + gc.Add(commitment.HidingNonce).Add(bindingNonce) + } + + return gc +} + +// SignatureShare represents a participants signature share, specifying which participant it was produced by. +type SignatureShare struct { + Identifier *group.Scalar + SignatureShare *group.Scalar +} + +// Encode returns a compact byte encoding of the signature share. +func (s SignatureShare) Encode() []byte { + id := s.Identifier.Encode() + share := s.SignatureShare.Encode() + + out := make([]byte, len(id)+len(share)) + copy(out, id) + copy(out[len(id):], share) + + return out +} + +// DecodeSignatureShare takes a byte string and attempts to decode it to return the signature share. +func (c Configuration) DecodeSignatureShare(data []byte) (*SignatureShare, error) { + g := c.Ciphersuite.Group + scalarLength := g.ScalarLength() + + if len(data) != 2*scalarLength { + return nil, errDecodeSignatureShare + } + + s := &SignatureShare{ + Identifier: g.NewScalar(), + SignatureShare: g.NewScalar(), + } + + if err := s.Identifier.Decode(data[:scalarLength]); err != nil { + return nil, fmt.Errorf("failed to decode signature share identifier: %w", err) + } + + if err := s.SignatureShare.Decode(data[scalarLength:]); err != nil { + return nil, fmt.Errorf("failed to decode signature share: %w", err) + } + + return s, nil } diff --git a/internal/schnorr/signature.go b/schnorr.go similarity index 74% rename from internal/schnorr/signature.go rename to schnorr.go index 3cfe8c7..5a51af3 100644 --- a/internal/schnorr/signature.go +++ b/schnorr.go @@ -6,8 +6,7 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -// Package schnorr provides Schnorr signature operations. -package schnorr +package frost import ( "fmt" @@ -49,22 +48,22 @@ func (s *Signature) Decode(g group.Group, encoded []byte) error { return nil } -// Challenge computes the per-message challenge. -func Challenge(cs internal.Ciphersuite, r, pk *group.Element, msg []byte) *group.Scalar { - commEnc := r.Encode() - pkEnc := pk.Encode() - challengeInput := internal.Concatenate(commEnc, pkEnc, msg) +// challenge computes the per-message challenge. +func challenge(cs internal.Ciphersuite, r, pk *group.Element, msg []byte) *group.Scalar { + return cs.H2(internal.Concatenate(r.Encode(), pk.Encode(), msg)) +} - return cs.H2(challengeInput) +func computeZ(r, challenge, key *group.Scalar) *group.Scalar { + return r.Add(challenge.Multiply(key)) } -// Sign returns the signature of message ms with the private key s. -func Sign(cs internal.Ciphersuite, msg []byte, s *group.Scalar) *Signature { +// Sign returns a Schnorr signature over the message msg with the full secret signing key (as opposed to a key share). +func Sign(cs internal.Ciphersuite, msg []byte, key *group.Scalar) *Signature { r := cs.Group.NewScalar().Random() R := cs.Group.Base().Multiply(r) - pk := cs.Group.Base().Multiply(s) - c := Challenge(cs, R, pk, msg) - z := r.Add(c.Multiply(s)) + pk := cs.Group.Base().Multiply(key) + c := challenge(cs, R, pk, msg) + z := computeZ(r, c, key) return &Signature{ R: R, @@ -74,7 +73,7 @@ func Sign(cs internal.Ciphersuite, msg []byte, s *group.Scalar) *Signature { // Verify returns whether the signature of the message msg is valid under the public key pk. func Verify(cs internal.Ciphersuite, msg []byte, signature *Signature, pk *group.Element) bool { - c := Challenge(cs, signature.R, pk, msg) + c := challenge(cs, signature.R, pk, msg) l := cs.Group.Base().Multiply(signature.Z) r := signature.R.Add(pk.Copy().Multiply(c)) diff --git a/tests/dkg_test.go b/tests/dkg_test.go index be1558b..2a97fb9 100644 --- a/tests/dkg_test.go +++ b/tests/dkg_test.go @@ -9,7 +9,6 @@ package frost_test import ( - "math/big" "testing" group "github.com/bytemare/crypto" @@ -17,6 +16,7 @@ import ( "github.com/bytemare/frost" "github.com/bytemare/frost/dkg" + "github.com/bytemare/frost/internal" ) // testUnit holds a participant and its return and input values during the protocol. @@ -30,76 +30,6 @@ type testUnit struct { r2InputData []*dkg.Round2Data } -func idFromInt(t *testing.T, g group.Group, i int) *group.Scalar { - id := g.NewScalar() - if err := id.SetInt(big.NewInt(int64(i))); err != nil { - t.Fatal(err) - } - - return id -} - -// dkgGenerateKeys generates sharded keys for maxSigner participant without a trusted dealer, and returns these shares -// and the group's public key. -func dkgGenerateKeys( - t *testing.T, - conf *frost.Configuration, - maxSigners, threshold int, -) ([]*secretsharing.KeyShare, *group.Element) { - g := conf.Ciphersuite.Group - - // Create participants. - participants := make([]*dkg.Participant, maxSigners) - for i := 0; i < maxSigners; i++ { - id := idFromInt(t, conf.Ciphersuite.Group, i+1) - participants[i] = dkg.NewParticipant(conf.Ciphersuite, id, maxSigners, threshold) - } - - // Step 1 & 2. - r1Data := make([]*dkg.Round1Data, maxSigners) - for i, p := range participants { - r1Data[i] = p.Init() - } - - // Step 3 & 4. - r2Data := make(map[string][]*dkg.Round2Data) - for _, p := range participants { - id := string(p.Identifier.Encode()) - r2Data[id] = make([]*dkg.Round2Data, 0, maxSigners-1) - } - - for _, p := range participants { - r2DataI, err := p.Continue(r1Data) - if err != nil { - t.Fatal(err) - } - - for _, r2d := range r2DataI { - id := string(r2d.ReceiverIdentifier.Encode()) - r2Data[id] = append(r2Data[id], r2d) - } - } - - // Step 5. - secretShares := make([]*secretsharing.KeyShare, maxSigners) - groupPublicKey := g.NewElement() - for i, p := range participants { - id := string(p.Identifier.Encode()) - secret, _, pk, err := p.Finalize(r1Data, r2Data[id]) - if err != nil { - t.Fatal(err) - } - - secretShares[i] = &secretsharing.KeyShare{ - Identifier: p.Identifier, - SecretKey: secret, - } - groupPublicKey = pk - } - - return secretShares, groupPublicKey -} - // TestDKG verifies // - execution of the protocol with any number of participants and threshold, and no errors. // - the correctness of each verification share. @@ -116,7 +46,7 @@ func TestDKG(t *testing.T) { // Vector of participant units. units := make([]*testUnit, maxSigners) for i := 0; i < maxSigners; i++ { - id := idFromInt(t, conf.Ciphersuite.Group, i+1) + id := internal.IntegerToScalar(conf.Ciphersuite.Group, i+1) units[i] = &testUnit{ participant: dkg.NewParticipant(conf.Ciphersuite, id, maxSigners, len(quals)), r2InputData: make([]*dkg.Round2Data, 0, maxSigners-1), @@ -188,7 +118,7 @@ func TestDKG(t *testing.T) { // Verify the individual secret shares by combining a subset of them. keyShares := make([]*secretsharing.KeyShare, len(quals)) for i, ii := range quals { - id := idFromInt(t, conf.Ciphersuite.Group, ii) + id := internal.IntegerToScalar(conf.Ciphersuite.Group, ii) for _, unit := range units { if id.Equal(unit.participant.Identifier) == 1 { @@ -236,3 +166,65 @@ func TestDKG_InvalidPOK(t *testing.T) { t.Fatal("expected error on invalid signature") } } + +// SimulateDKG generates sharded keys for maxSigners participant without a trusted dealer, and returns these shares +// and the group's public key. This function is used in tests and examples. +func SimulateDKG( + conf *frost.Configuration, + maxSigners, threshold int, +) ([]*secretsharing.KeyShare, []*group.Element, *group.Element) { + g := conf.Ciphersuite.Group + + // Create participants. + participants := make([]*dkg.Participant, maxSigners) + for i := 0; i < maxSigners; i++ { + id := internal.IntegerToScalar(conf.Ciphersuite.Group, i+1) + participants[i] = dkg.NewParticipant(conf.Ciphersuite, id, maxSigners, threshold) + } + + // Step 1 & 2. + r1Data := make([]*dkg.Round1Data, maxSigners) + for i, p := range participants { + r1Data[i] = p.Init() + } + + // Step 3 & 4. + r2Data := make(map[string][]*dkg.Round2Data) + for _, p := range participants { + id := string(p.Identifier.Encode()) + r2Data[id] = make([]*dkg.Round2Data, 0, maxSigners-1) + } + + for _, p := range participants { + r2DataI, err := p.Continue(r1Data) + if err != nil { + panic(err) + } + + for _, r2d := range r2DataI { + id := string(r2d.ReceiverIdentifier.Encode()) + r2Data[id] = append(r2Data[id], r2d) + } + } + + // Step 5. + secretShares := make([]*secretsharing.KeyShare, maxSigners) + publicShares := make([]*group.Element, maxSigners) + groupPublicKey := g.NewElement() + for i, p := range participants { + id := string(p.Identifier.Encode()) + secret, public, pk, err := p.Finalize(r1Data, r2Data[id]) + if err != nil { + panic(err) + } + + secretShares[i] = &secretsharing.KeyShare{ + Identifier: p.Identifier, + SecretKey: secret, + } + publicShares[i] = public + groupPublicKey = pk + } + + return secretShares, publicShares, groupPublicKey +} diff --git a/tests/frost_test.go b/tests/frost_test.go index 3912c66..9887495 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -17,14 +17,13 @@ import ( "github.com/bytemare/frost" "github.com/bytemare/frost/internal" - "github.com/bytemare/frost/internal/schnorr" ) var configurationTable = []frost.Configuration{ { GroupPublicKey: nil, Ciphersuite: internal.Ciphersuite{ - ContextString: []byte("FROST-ED25519-SHA512-v11"), + ContextString: []byte("FROST-ED25519-SHA512-v1"), Hash: hash.SHA512, Group: group.Edwards25519Sha512, }, @@ -33,7 +32,7 @@ var configurationTable = []frost.Configuration{ Ciphersuite: internal.Ciphersuite{ Group: group.Ristretto255Sha512, Hash: hash.SHA512, - ContextString: []byte("FROST-RISTRETTO255-SHA512-v11"), + ContextString: []byte("FROST-RISTRETTO255-SHA512-v1"), }, GroupPublicKey: nil, }, @@ -41,14 +40,14 @@ var configurationTable = []frost.Configuration{ Ciphersuite: internal.Ciphersuite{ Group: group.P256Sha256, Hash: hash.SHA256, - ContextString: []byte("FROST-P256-SHA256-v11"), + ContextString: []byte("FROST-P256-SHA256-v1"), }, GroupPublicKey: nil, }, { GroupPublicKey: nil, Ciphersuite: internal.Ciphersuite{ - ContextString: []byte("FROST-secp256k1-SHA256-v11"), + ContextString: []byte("FROST-secp256k1-SHA256-v1"), Hash: hash.SHA256, Group: group.Secp256k1, }, @@ -99,7 +98,7 @@ func TestTrustedDealerKeygen(t *testing.T) { configuration.GroupPublicKey = dealerGroupPubKey for i, shareI := range privateKeyShares { - if !frost.Verify(g, shareI, secretsharingCommitment) { + if !frost.VerifyVSS(g, shareI, secretsharingCommitment) { t2.Fatal(i) } } @@ -126,16 +125,13 @@ func TestFrost(t *testing.T) { testAll(t, func(t2 *testing.T, configuration *frost.Configuration) { g := configuration.Ciphersuite.Group - privateKeyShares, groupPublicKey := dkgGenerateKeys(t, configuration, max, threshold) + privateKeyShares, _, groupPublicKey := SimulateDKG(configuration, max, threshold) configuration.GroupPublicKey = groupPublicKey // Create Participants participants := make(ParticipantList, max) for i, share := range privateKeyShares { - participants[i] = &frost.Participant{ - Configuration: *configuration, - ParticipantInfo: frost.ParticipantInfo{KeyShare: share}, - } + participants[i] = configuration.Participant(share.Identifier, share.SecretKey) } signatureAggregator := &frost.Participant{ @@ -148,17 +144,16 @@ func TestFrost(t *testing.T) { participantList[i] = internal.IntegerToScalar(g, p) } - comList := make(internal.CommitmentList, len(participantList)) + comList := make(frost.CommitmentList, len(participantList)) for i, id := range participantList { p := participants.Get(id) comList[i] = p.Commit() } comList.Sort() - _, _ = comList.ComputeBindingFactors(configuration.Ciphersuite, message) // Round Two: Sign - sigShares := make([]*group.Scalar, len(participantList)) + sigShares := make([]*frost.SignatureShare, len(participantList)) for i, id := range participantList { p := participants.Get(id) @@ -179,8 +174,8 @@ func TestFrost(t *testing.T) { t.Fatal(err) } - singleSig := schnorr.Sign(configuration.Ciphersuite, message, groupSecretKey) - if !schnorr.Verify(configuration.Ciphersuite, message, singleSig, groupPublicKey) { + singleSig := frost.Sign(configuration.Ciphersuite, message, groupSecretKey) + if !frost.Verify(configuration.Ciphersuite, message, singleSig, groupPublicKey) { t2.Fatal() } }) diff --git a/tests/vector_utils_test.go b/tests/vector_utils_test.go index 8025157..e6d3ebd 100644 --- a/tests/vector_utils_test.go +++ b/tests/vector_utils_test.go @@ -34,15 +34,6 @@ func (p ParticipantList) Get(id *group.Scalar) *frost.Participant { return nil } -type testVectorConfig struct { - MaxParticipants string `json:"MAX_PARTICIPANTS"` - NumParticipants string `json:"NUM_PARTICIPANTS"` - MinParticipants string `json:"MIN_PARTICIPANTS"` - Name string `json:"name"` - Group string `json:"group"` - Hash string `json:"hash"` -} - func stringToInt(t *testing.T, s string) int { i, err := strconv.ParseInt(s, 10, 32) if err != nil { @@ -52,60 +43,6 @@ func stringToInt(t *testing.T, s string) int { return int(i) } -func configToConfiguration(t *testing.T, c *testVectorConfig) *frost.Configuration { - switch c.Group { - case "ed25519": - return frost.Ed25519.Configuration() - case "ristretto255": - return frost.Ristretto255.Configuration() - case "P-256": - return frost.P256.Configuration() - case "secp256k1": - return frost.Secp256k1.Configuration() - default: - t.Fatalf("group not supported: %s", c.Group) - } - - return nil -} - -func (c testVectorConfig) decode(t *testing.T) *testConfig { - return &testConfig{ - MaxParticipants: stringToInt(t, c.MaxParticipants), - NumParticipants: stringToInt(t, c.NumParticipants), - MinParticipants: stringToInt(t, c.MinParticipants), - Name: c.Name, - Configuration: configToConfiguration(t, &c), - } -} - -type testConfig struct { - *frost.Configuration - Name string - ContextString []byte - MaxParticipants int - NumParticipants int - MinParticipants int -} - -type testVectorInput struct { - GroupSecretKey ByteToHex `json:"group_secret_key"` - GroupPublicKey ByteToHex `json:"group_public_key"` - Message ByteToHex `json:"message"` - SharePolynomialCoefficients []ByteToHex `json:"share_polynomial_coefficients"` - Participants struct { - Num1 struct { - ParticipantShare ByteToHex `json:"participant_share"` - } `json:"1"` - Num2 struct { - ParticipantShare ByteToHex `json:"participant_share"` - } `json:"2"` - Num3 struct { - ParticipantShare ByteToHex `json:"participant_share"` - } `json:"3"` - } `json:"participants"` -} - func decodeScalar(t *testing.T, g group.Group, enc []byte) *group.Scalar { scalar := g.NewScalar() if err := scalar.Decode(enc); err != nil { @@ -124,41 +61,69 @@ func decodeElement(t *testing.T, g group.Group, enc []byte) *group.Element { return element } -func (i testVectorInput) decode(t *testing.T, g group.Group) *testInput { - input := &testInput{ - GroupSecretKey: decodeScalar(t, g, i.GroupSecretKey), - GroupPublicKey: decodeElement(t, g, i.GroupPublicKey), - Message: i.Message, - SharePolynomialCoefficients: make([]*group.Scalar, len(i.SharePolynomialCoefficients)), - Participants: make([]*secretsharing.KeyShare, 3), - } +type ByteToHex []byte - for j, coeff := range i.SharePolynomialCoefficients { - input.SharePolynomialCoefficients[j] = decodeScalar(t, g, coeff) - } +func (j ByteToHex) MarshalJSON() ([]byte, error) { + return json.Marshal(hex.EncodeToString(j)) +} - input.Participants[0] = &secretsharing.KeyShare{ - Identifier: internal.IntegerToScalar(g, 1), - SecretKey: decodeScalar(t, g, i.Participants.Num1.ParticipantShare), - } - input.Participants[1] = &secretsharing.KeyShare{ - Identifier: internal.IntegerToScalar(g, 2), - SecretKey: decodeScalar(t, g, i.Participants.Num2.ParticipantShare), - } - input.Participants[2] = &secretsharing.KeyShare{ - Identifier: internal.IntegerToScalar(g, 3), - SecretKey: decodeScalar(t, g, i.Participants.Num3.ParticipantShare), +func (j *ByteToHex) UnmarshalJSON(b []byte) error { + bs := strings.Trim(string(b), "\"") + + dst, err := hex.DecodeString(bs) + if err != nil { + return err } - return input + *j = dst + return nil } -type testInput struct { - GroupSecretKey *group.Scalar - GroupPublicKey *group.Element - Message []byte - SharePolynomialCoefficients []*group.Scalar - Participants []*secretsharing.KeyShare +/* + Test vectors as in the .json files +*/ + +type testVectorInput struct { + ParticipantList []int `json:"participant_list"` + GroupSecretKey ByteToHex `json:"group_secret_key"` + GroupPublicKey ByteToHex `json:"group_public_key"` + Message ByteToHex `json:"message"` + SharePolynomialCoefficients []ByteToHex `json:"share_polynomial_coefficients"` + ParticipantShares []testVectorParticipantShare `json:"participant_shares"` +} + +type testVector struct { + Config *testVectorConfig `json:"config"` + Inputs *testVectorInput `json:"inputs"` + RoundOneOutputs *testVectorRoundOneOutputs `json:"round_one_outputs"` + RoundTwoOutputs *testVectorRoundTwoOutputs `json:"round_two_outputs"` + FinalOutput struct { + Sig ByteToHex `json:"sig"` + } `json:"final_output"` +} + +type testVectorConfig struct { + MaxParticipants string `json:"MAX_PARTICIPANTS"` + NumParticipants string `json:"NUM_PARTICIPANTS"` + MinParticipants string `json:"MIN_PARTICIPANTS"` + Name string `json:"name"` + Group string `json:"group"` + Hash string `json:"hash"` +} + +func (c testVectorConfig) decode(t *testing.T) *testConfig { + return &testConfig{ + MaxParticipants: stringToInt(t, c.MaxParticipants), + NumParticipants: stringToInt(t, c.NumParticipants), + MinParticipants: stringToInt(t, c.MinParticipants), + Name: c.Name, + Configuration: configToConfiguration(t, &c), + } +} + +type testVectorParticipantShare struct { + ParticipantShare ByteToHex `json:"participant_share"` + Identifier int `json:"identifier"` } type testParticipant struct { @@ -170,63 +135,50 @@ type testParticipant struct { BindingNonceCommitment ByteToHex `json:"binding_nonce_commitment"` BindingFactorInput ByteToHex `json:"binding_factor_input"` BindingFactor ByteToHex `json:"binding_factor"` -} - -func decodeParticipant(t *testing.T, g group.Group, id int, tp *testParticipant) *participant { - return &participant{ - ID: internal.IntegerToScalar(g, id), - HidingNonceRandomness: tp.HidingNonceRandomness, - BindingNonceRandomness: tp.BindingNonceRandomness, - HidingNonce: decodeScalar(t, g, tp.HidingNonce), - BindingNonce: decodeScalar(t, g, tp.BindingNonce), - HidingNonceCommitment: decodeElement(t, g, tp.HidingNonceCommitment), - BindingNonceCommitment: decodeElement(t, g, tp.BindingNonceCommitment), - BindingFactorInput: tp.BindingFactorInput, - BindingFactor: decodeScalar(t, g, tp.BindingFactor), - } + Identifier int `json:"identifier"` } type testVectorRoundOneOutputs struct { - ParticipantList string `json:"participant_list"` - Participants struct { - Num1 testParticipant `json:"1"` - Num3 testParticipant `json:"3"` - } `json:"participants"` + Outputs []testParticipant `json:"outputs"` } -func splitIDString(s string) []int { - split := func(r rune) bool { - return r == ',' || r == ' ' - } - - str := strings.FieldsFunc(s, split) - ints := make([]int, len(str)) - for i, e := range str { - j, err := strconv.Atoi(e) - if err != nil { - panic(nil) - } - ints[i] = j - } +type testVectorSigShares struct { + SigShare ByteToHex `json:"sig_share"` + Identifier int `json:"identifier"` +} - return ints +type testVectorRoundTwoOutputs struct { + Outputs []testVectorSigShares `json:"outputs"` } -func (o testVectorRoundOneOutputs) decode(t *testing.T, g group.Group) *testRoundOneOutputs { - ids := splitIDString(o.ParticipantList) - r := &testRoundOneOutputs{ - ParticipantList: make([]*group.Scalar, len(ids)), - Participants: make([]*participant, 2), - } +/* + Parsed and deserialized vectors +*/ - for i, id := range ids { - r.ParticipantList[i] = internal.IntegerToScalar(g, id) - } +type testConfig struct { + *frost.Configuration + Name string + ContextString []byte + MaxParticipants int + NumParticipants int + MinParticipants int +} - r.Participants[0] = decodeParticipant(t, g, 1, &o.Participants.Num1) - r.Participants[1] = decodeParticipant(t, g, 3, &o.Participants.Num3) +type testInput struct { + ParticipantList []*group.Scalar + GroupSecretKey *group.Scalar + GroupPublicKey *group.Element + Message []byte + SharePolynomialCoefficients []*group.Scalar + Participants []*secretsharing.KeyShare +} - return r +type test struct { + Config *testConfig + Inputs *testInput + RoundOneOutputs *testRoundOneOutputs + RoundTwoOutputs *testRoundTwoOutputs + FinalOutput []byte } type participant struct { @@ -242,58 +194,101 @@ type participant struct { } type testRoundOneOutputs struct { - ParticipantList []*group.Scalar - Participants []*participant + Outputs []*participant } -type testVectorRoundTwoOutputs struct { - ParticipantList string `json:"participant_list"` - Participants struct { - Num1 struct { - SigShare ByteToHex `json:"sig_share"` - } `json:"1"` - Num3 struct { - SigShare ByteToHex `json:"sig_share"` - } `json:"3"` - } `json:"participants"` +type testRoundTwoOutputs struct { + Outputs []*secretsharing.KeyShare } -func (o testVectorRoundTwoOutputs) decode(t *testing.T, g group.Group) *testRoundTwoOutputs { - ids := splitIDString(o.ParticipantList) - r := &testRoundTwoOutputs{ - make([]*group.Scalar, len(ids)), - make([]*secretsharing.KeyShare, len(ids)), +/* + Parsing and decoding functions. +*/ + +func configToConfiguration(t *testing.T, c *testVectorConfig) *frost.Configuration { + switch c.Group { + case "ed25519": + return frost.Ed25519.Configuration() + case "ristretto255": + return frost.Ristretto255.Configuration() + case "P-256": + return frost.P256.Configuration() + case "secp256k1": + return frost.Secp256k1.Configuration() + default: + t.Fatalf("group not supported: %s", c.Group) + } + + return nil +} + +func decodeParticipant(t *testing.T, g group.Group, tp *testParticipant) *participant { + return &participant{ + ID: internal.IntegerToScalar(g, tp.Identifier), + HidingNonceRandomness: tp.HidingNonceRandomness, + BindingNonceRandomness: tp.BindingNonceRandomness, + HidingNonce: decodeScalar(t, g, tp.HidingNonce), + BindingNonce: decodeScalar(t, g, tp.BindingNonce), + HidingNonceCommitment: decodeElement(t, g, tp.HidingNonceCommitment), + BindingNonceCommitment: decodeElement(t, g, tp.BindingNonceCommitment), + BindingFactorInput: tp.BindingFactorInput, + BindingFactor: decodeScalar(t, g, tp.BindingFactor), + } +} + +func (i testVectorInput) decode(t *testing.T, g group.Group) *testInput { + input := &testInput{ + GroupSecretKey: decodeScalar(t, g, i.GroupSecretKey), + GroupPublicKey: decodeElement(t, g, i.GroupPublicKey), + Message: i.Message, + SharePolynomialCoefficients: make([]*group.Scalar, len(i.SharePolynomialCoefficients)), + Participants: make([]*secretsharing.KeyShare, len(i.ParticipantShares)), + ParticipantList: make([]*group.Scalar, len(i.ParticipantList)), } - for i, id := range ids { - r.ParticipantList[i] = internal.IntegerToScalar(g, id) + for j, id := range i.ParticipantList { + input.ParticipantList[j] = internal.IntegerToScalar(g, id) } - r.Participants[0] = &secretsharing.KeyShare{ - Identifier: internal.IntegerToScalar(g, 1), - SecretKey: decodeScalar(t, g, o.Participants.Num1.SigShare), + for j, coeff := range i.SharePolynomialCoefficients { + input.SharePolynomialCoefficients[j] = decodeScalar(t, g, coeff) } - r.Participants[1] = &secretsharing.KeyShare{ - Identifier: internal.IntegerToScalar(g, 3), - SecretKey: decodeScalar(t, g, o.Participants.Num3.SigShare), + + for j, p := range i.ParticipantShares { + input.Participants[j] = &secretsharing.KeyShare{ + Identifier: internal.IntegerToScalar(g, p.Identifier), + SecretKey: decodeScalar(t, g, p.ParticipantShare), + } } - return r + return input } -type testRoundTwoOutputs struct { - ParticipantList []*group.Scalar - Participants []*secretsharing.KeyShare +func (o testVectorRoundOneOutputs) decode(t *testing.T, g group.Group) *testRoundOneOutputs { + r := &testRoundOneOutputs{ + Outputs: make([]*participant, len(o.Outputs)), + } + + for i, p := range o.Outputs { + r.Outputs[i] = decodeParticipant(t, g, &p) + } + + return r } -type testVector struct { - Config *testVectorConfig `json:"config"` - Inputs *testVectorInput `json:"inputs"` - RoundOneOutputs *testVectorRoundOneOutputs `json:"round_one_outputs"` - RoundTwoOutputs *testVectorRoundTwoOutputs `json:"round_two_outputs"` - FinalOutput struct { - Sig ByteToHex `json:"sig"` - } `json:"final_output"` +func (o testVectorRoundTwoOutputs) decode(t *testing.T, g group.Group) *testRoundTwoOutputs { + r := &testRoundTwoOutputs{ + Outputs: make([]*secretsharing.KeyShare, len(o.Outputs)), + } + + for i, p := range o.Outputs { + r.Outputs[i] = &secretsharing.KeyShare{ + Identifier: internal.IntegerToScalar(g, p.Identifier), + SecretKey: decodeScalar(t, g, p.SigShare), + } + } + + return r } func (v testVector) decode(t *testing.T) *test { @@ -306,29 +301,3 @@ func (v testVector) decode(t *testing.T) *test { FinalOutput: v.FinalOutput.Sig, } } - -type test struct { - Config *testConfig - Inputs *testInput - RoundOneOutputs *testRoundOneOutputs - RoundTwoOutputs *testRoundTwoOutputs - FinalOutput []byte -} - -type ByteToHex []byte - -func (j ByteToHex) MarshalJSON() ([]byte, error) { - return json.Marshal(hex.EncodeToString(j)) -} - -func (j *ByteToHex) UnmarshalJSON(b []byte) error { - bs := strings.Trim(string(b), "\"") - - dst, err := hex.DecodeString(bs) - if err != nil { - return err - } - - *j = dst - return nil -} diff --git a/tests/vectors/frost-ed25519-sha512.json b/tests/vectors/frost-ed25519-sha512.json index b0ebb1d..9bf89d8 100644 --- a/tests/vectors/frost-ed25519-sha512.json +++ b/tests/vectors/frost-ed25519-sha512.json @@ -8,61 +8,70 @@ "hash": "SHA-512" }, "inputs": { + "participant_list": [ + 1, + 3 + ], "group_secret_key": "7b1c33d3f5291d85de664833beb1ad469f7fb6025a0ec78b3a790c6e13a98304", "group_public_key": "15d21ccd7ee42959562fc8aa63224c8851fb3ec85a3faf66040d380fb9738673", "message": "74657374", "share_polynomial_coefficients": [ "178199860edd8c62f5212ee91eff1295d0d670ab4ed4506866bae57e7030b204" ], - "participants": { - "1": { + "participant_shares": [ + { + "identifier": 1, "participant_share": "929dcc590407aae7d388761cddb0c0db6f5627aea8e217f4a033f2ec83d93509" }, - "2": { + { + "identifier": 2, "participant_share": "a91e66e012e4364ac9aaa405fcafd370402d9859f7b6685c07eed76bf409e80d" }, - "3": { + { + "identifier": 3, "participant_share": "d3cb090a075eb154e82fdb4b3cb507f110040905468bb9c46da8bdea643a9a02" } - } + ] }, "round_one_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "hiding_nonce_randomness": "9d06a6381c7a4493929761a73692776772b274236fb5cfcc7d1b48ac3a9c249f", - "binding_nonce_randomness": "db184d7bc01a3417fe1f2eb3cf5479bb027145e6369a5f879f32d334ab256b23", - "hiding_nonce": "70652da3e8d7533a0e4b9e9104f01b48c396b5b553717784ed8d05c6a36b9609", - "binding_nonce": "4f9e1ad260b5c0e4fe0e0719c6324f89fecd053758f77c957f56967e634a710e", - "hiding_nonce_commitment": "44105304351ceddc58e15ddea35b2cb48e60ced54ceb22c3b0e5d42d098aa1d8", - "binding_nonce_commitment": "b8274b18a12f2cef74ae42f876cec1e31daab5cb162f95a56cd2487409c9d1dd", - "binding_factor_input": "c5b95020cba31a9035835f074f718d0c3af02a318d6b4723bbd1c088f4889dd7b9ff8e79f9a67a9d27605144259a7af18b7cca2539ffa5c4f1366a98645da8f4e077d604fff64f20e2377a37e5a10ce152194d62fe856ef4cd935d4f1cb0088c2083a2722ad3f5a84d778e257da0df2a7cadb004b1f5528352af778b94ee1c2a0100000000000000000000000000000000000000000000000000000000000000", - "binding_factor": "2d5630c36d33258b1208c4205fa759b762d09bfa06b29cf792cf98758c0b3305" + "outputs": [ + { + "identifier": 1, + "hiding_nonce_randomness": "0fd2e39e111cdc266f6c0f4d0fd45c947761f1f5d3cb583dfcb9bbaf8d4c9fec", + "binding_nonce_randomness": "69cd85f631d5f7f2721ed5e40519b1366f340a87c2f6856363dbdcda348a7501", + "hiding_nonce": "812d6104142944d5a55924de6d49940956206909f2acaeedecda2b726e630407", + "binding_nonce": "b1110165fc2334149750b28dd813a39244f315cff14d4e89e6142f262ed83301", + "hiding_nonce_commitment": "b5aa8ab305882a6fc69cbee9327e5a45e54c08af61ae77cb8207be3d2ce13de3", + "binding_nonce_commitment": "67e98ab55aa310c3120418e5050c9cf76cf387cb20ac9e4b6fdb6f82a469f932", + "binding_factor_input": "15d21ccd7ee42959562fc8aa63224c8851fb3ec85a3faf66040d380fb9738673504df914fa965023fb75c25ded4bb260f417de6d32e5c442c6ba313791cc9a4948d6273e8d3511f93348ea7a708a9b862bc73ba2a79cfdfe07729a193751cbc973af46d8ac3440e518d4ce440a0e7d4ad5f62ca8940f32de6d8dc00fc12c660b817d587d82f856d277ce6473cae6d2f5763f7da2e8b4d799a3f3e725d4522ec70100000000000000000000000000000000000000000000000000000000000000", + "binding_factor": "f2cb9d7dd9beff688da6fcc83fa89046b3479417f47f55600b106760eb3b5603" }, - "3": { - "hiding_nonce_randomness": "31ca9b07936d6b342a43d97f23b7bec5a5f5a09575a075393868dd8df5c05a54", - "binding_nonce_randomness": "c1db96a85d8b593e14fdb869c0955625478afa6a987ad217e7f2261dcab26819", - "hiding_nonce": "233adcb0ec0eddba5f1cc5268f3f4e6fc1dd97fb1e4a1754e6ddc92ed834ca0b", - "binding_nonce": "b59fc8a32fe02ec0a44c4671f3d1f82ea3924b7c7c0179398fc9137e82757803", - "hiding_nonce_commitment": "d31bd81ce216b1c83912803a574a0285796275cb8b14f6dc92c8b09a6951f0a2", - "binding_nonce_commitment": "e1c863cfd08df775b6747ef2456e9bf9a03cc281a479a95261dc39137fcf0967", - "binding_factor_input": "c5b95020cba31a9035835f074f718d0c3af02a318d6b4723bbd1c088f4889dd7b9ff8e79f9a67a9d27605144259a7af18b7cca2539ffa5c4f1366a98645da8f4e077d604fff64f20e2377a37e5a10ce152194d62fe856ef4cd935d4f1cb0088c2083a2722ad3f5a84d778e257da0df2a7cadb004b1f5528352af778b94ee1c2a0300000000000000000000000000000000000000000000000000000000000000", - "binding_factor": "1137be5cdf3d18e44367acee8485e9a66c3164077af80619b6291e3943bbef04" + { + "identifier": 3, + "hiding_nonce_randomness": "86d64a260059e495d0fb4fcc17ea3da7452391baa494d4b00321098ed2a0062f", + "binding_nonce_randomness": "13e6b25afb2eba51716a9a7d44130c0dbae0004a9ef8d7b5550c8a0e07c61775", + "hiding_nonce": "c256de65476204095ebdc01bd11dc10e57b36bc96284595b8215222374f99c0e", + "binding_nonce": "243d71944d929063bc51205714ae3c2218bd3451d0214dfb5aeec2a90c35180d", + "hiding_nonce_commitment": "cfbdb165bd8aad6eb79deb8d287bcc0ab6658ae57fdcc98ed12c0669e90aec91", + "binding_nonce_commitment": "7487bc41a6e712eea2f2af24681b58b1cf1da278ea11fe4e8b78398965f13552", + "binding_factor_input": "15d21ccd7ee42959562fc8aa63224c8851fb3ec85a3faf66040d380fb9738673504df914fa965023fb75c25ded4bb260f417de6d32e5c442c6ba313791cc9a4948d6273e8d3511f93348ea7a708a9b862bc73ba2a79cfdfe07729a193751cbc973af46d8ac3440e518d4ce440a0e7d4ad5f62ca8940f32de6d8dc00fc12c660b817d587d82f856d277ce6473cae6d2f5763f7da2e8b4d799a3f3e725d4522ec70300000000000000000000000000000000000000000000000000000000000000", + "binding_factor": "b087686bf35a13f3dc78e780a34b0fe8a77fef1b9938c563f5573d71d8d7890f" } - } + ] }, "round_two_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "sig_share": "c4b26af1e91fbc8440a0dad253e72620da624553c5b625fd51e6ea179fc09f05" + "outputs": [ + { + "identifier": 1, + "sig_share": "001719ab5a53ee1a12095cd088fd149702c0720ce5fd2f29dbecf24b7281b603" }, - "3": { - "sig_share": "9369640967d0cb98f4dedfde58a845e0e18e0a7164396358439060ed282b4e08" + { + "identifier": 3, + "sig_share": "bd86125de990acc5e1f13781d8e32c03a9bbd4c53539bbc106058bfd14326007" } - } + ] }, "final_output": { - "sig": "ae11c539fdc709b78fef5ee1f5a2250297e3e1b62a86a86c26d93c389934ba0e571ccffa50f0871d357fbab1ac8f6c00bcf14fc429f0885595764b05c8ebed0d" + "sig": "36282629c383bb820a88b71cae937d41f2f2adfcc3d02e55507e2fb9e2dd3cbebd9d2b0844e49ae0f3fa935161e1419aab7b47d21a37ebeae1f17d4987b3160b" } } \ No newline at end of file diff --git a/tests/vectors/frost-p256-sha256.json b/tests/vectors/frost-p256-sha256.json index db6be1f..fdad941 100644 --- a/tests/vectors/frost-p256-sha256.json +++ b/tests/vectors/frost-p256-sha256.json @@ -8,61 +8,70 @@ "hash": "SHA-256" }, "inputs": { + "participant_list": [ + 1, + 3 + ], "group_secret_key": "8ba9bba2e0fd8c4767154d35a0b7562244a4aaf6f36c8fb8735fa48b301bd8de", "group_public_key": "023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99cfbd02b4dc00585ec45ab70", "message": "74657374", "share_polynomial_coefficients": [ "80f25e6c0709353e46bfbe882a11bdbb1f8097e46340eb8673b7e14556e6c3a4" ], - "participants": { - "1": { + "participant_shares": [ + { + "identifier": 1, "participant_share": "0c9c1a0fe806c184add50bbdcac913dda73e482daf95dcb9f35dbb0d8a9f7731" }, - "2": { + { + "identifier": 2, "participant_share": "8d8e787bef0ff6c2f494ca45f4dad198c6bee01212d6c84067159c52e1863ad5" }, - "3": { + { + "identifier": 3, "participant_share": "0e80d6e8f6192c003b5488ce1eec8f5429587d48cf001541e713b2d53c09d928" } - } + ] }, "round_one_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "hiding_nonce_randomness": "f4e8cf80aec3f888d997900ac7e3e349944b5a6b47649fc32186d2f1238103c6", - "binding_nonce_randomness": "a7f220770b6f10ff54ec6afa55f99bd08cc92fa1a488c86e9bf493e9cb894cdf", - "hiding_nonce": "f871dfcf6bcd199342651adc361b92c941cb6a0d8c8c1a3b91d79e2c1bf3722d", - "binding_nonce": "bd3ece3634a1b303dea0586ed67a91fe68510f11ebe66e8868309b1551ef2388", - "hiding_nonce_commitment": "03987febbc67a8ed735affdff4d3a5adf22c05c80f97f311ab7437a3027372deb3", - "binding_nonce_commitment": "02a1960477d139035b986d6adcb06491378beb92ccd097ad94e76291c52343849d", - "binding_factor_input": "350c8b523feea9bb35720e9fbe0405ed48d78caa4fb60869f34367e144c68bb0fc77bf512409ad8b91e2ace4909229891a446c45683f5eb2f843dbec224527dc0000000000000000000000000000000000000000000000000000000000000001", - "binding_factor": "cb415dd1d866493ee7d2db7cb33929d7e430e84d80c58070e2bbb1fdbf76a9c8" + "outputs": [ + { + "identifier": 1, + "hiding_nonce_randomness": "ec4c891c85fee802a9d757a67d1252e7f4e5efb8a538991ac18fbd0e06fb6fd3", + "binding_nonce_randomness": "9334e29d09061223f69a09421715a347e4e6deba77444c8f42b0c833f80f4ef9", + "hiding_nonce": "9f0542a5ba879a58f255c09f06da7102ef6a2dec6279700c656d58394d8facd4", + "binding_nonce": "6513dfe7429aa2fc972c69bb495b27118c45bbc6e654bb9dc9be55385b55c0d7", + "hiding_nonce_commitment": "0213b3e6298bf8ad46fd5e9389519a8665d63d98f4ec6a1fcca434e809d2d8070e", + "binding_nonce_commitment": "02188ff1390bf69374d7b272e454b1878ef10a6b6ea3ff36f114b300b4dbd5233b", + "binding_factor_input": "023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99cfbd02b4dc00585ec45ab70825371853e974bc30ac5b947b216d70461919666584c70c51f9f56f117736c5d178dd0b521ad9c1abe98048419cbdec81504c85e12eb40e3bcb6ec73d3fc4afd0000000000000000000000000000000000000000000000000000000000000001", + "binding_factor": "7925f0d4693f204e6e59233e92227c7124664a99739d2c06b81cf64ddf90559e" }, - "3": { - "hiding_nonce_randomness": "1b6149d252a0a0a6618b8d22a1c49897f9b0d23a48f19598e191e05dc7b7ae33", - "binding_nonce_randomness": "e13994bb75aafe337c32afdbfd08ae60dd108fc768845edaa871992044cabf1b", - "hiding_nonce": "802e9321f9f63688c6c1a9681a4a4661f71770e0cef92b8a5997155d18fb82ef", - "binding_nonce": "8b6b692ae634a24536f45dda95b2398af71cd605fb7a0bbdd9408d211ab99eba", - "hiding_nonce_commitment": "0212cac45ebd4100c97506939391f9be4ffc3ca2960e2ef95aeaa38abdede204ca", - "binding_nonce_commitment": "03017ce754d310eabda0f5681e61ce3d713cdd337070faa6a68471af49694a4e7e", - "binding_factor_input": "350c8b523feea9bb35720e9fbe0405ed48d78caa4fb60869f34367e144c68bb0fc77bf512409ad8b91e2ace4909229891a446c45683f5eb2f843dbec224527dc0000000000000000000000000000000000000000000000000000000000000003", - "binding_factor": "dfd82467569334e952edecb10d92adf85b8e299db0b40be3131a12efdfa3e796" + { + "identifier": 3, + "hiding_nonce_randomness": "c0451c5a0a5480d6c1f860e5db7d655233dca2669fd90ff048454b8ce983367b", + "binding_nonce_randomness": "2ba5f7793ae700e40e78937a82f407dd35e847e33d1e607b5c7eb6ed2a8ed799", + "hiding_nonce": "f73444a8972bcda9e506bbca3d2b1c083c10facdf4bb5d47fef7c2dc1d9f2a0d", + "binding_nonce": "44c6a29075d6e7e4f8b97796205f9e22062e7835141470afe9417fd317c1c303", + "hiding_nonce_commitment": "033ac9a5fe4a8b57316ba1c34e8a6de453033b750e8984924a984eb67a11e73a3f", + "binding_nonce_commitment": "03a7a2480ee16199262e648aea3acab628a53e9b8c1945078f2ddfbdc98b7df369", + "binding_factor_input": "023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99cfbd02b4dc00585ec45ab70825371853e974bc30ac5b947b216d70461919666584c70c51f9f56f117736c5d178dd0b521ad9c1abe98048419cbdec81504c85e12eb40e3bcb6ec73d3fc4afd0000000000000000000000000000000000000000000000000000000000000003", + "binding_factor": "e10d24a8a403723bcb6f9bb4c537f316593683b472f7a89f166630dde11822c4" } - } + ] }, "round_two_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "sig_share": "c5acd980310aaf87cb7a9a90428698ef3e6b1e5860f7fb06329bc0efe3f14ca5" + "outputs": [ + { + "identifier": 1, + "sig_share": "400308eaed7a2ddee02a265abe6a1cfe04d946ee8720768899619cfabe7a3aeb" }, - "3": { - "sig_share": "1e064fbd35467377eb3fe161ff975e9ec3ed8e2e0d4c73f3a6b0a023777e1264" + { + "identifier": 3, + "sig_share": "561da3c179edbb0502d941bb3e3ace3c37d122aaa46fb54499f15f3a3331de44" } - } + ] }, "final_output": { - "sig": "029e07d4171dbf9a730ed95e9d95bda06fa4db76c88c519f7f3ca5483019f46cb0e3b3293d665122ffb6ba7bf2421df78e0258ac866e446ef9d94c61135b6f5f09" + "sig": "026d8d434874f87bdb7bc0dfd239b2c00639044f9dcb195e9a04426f70bfa4b70d9620acac6767e8e3e3036815fca4eb3a3caa69992b902bcd3352fc34f1ac192f" } } \ No newline at end of file diff --git a/tests/vectors/frost-ristretto255-sha512.json b/tests/vectors/frost-ristretto255-sha512.json index f82c0d5..5e39a55 100644 --- a/tests/vectors/frost-ristretto255-sha512.json +++ b/tests/vectors/frost-ristretto255-sha512.json @@ -8,61 +8,70 @@ "hash": "SHA-512" }, "inputs": { + "participant_list": [ + 1, + 3 + ], "group_secret_key": "1b25a55e463cfd15cf14a5d3acc3d15053f08da49c8afcf3ab265f2ebc4f970b", "group_public_key": "e2a62f39eede11269e3bd5a7d97554f5ca384f9f6d3dd9c3c0d05083c7254f57", "message": "74657374", "share_polynomial_coefficients": [ "410f8b744b19325891d73736923525a4f596c805d060dfb9c98009d34e3fec02" ], - "participants": { - "1": { + "participant_shares": [ + { + "identifier": 1, "participant_share": "5c3430d391552f6e60ecdc093ff9f6f4488756aa6cebdbad75a768010b8f830e" }, - "2": { + { + "identifier": 2, "participant_share": "b06fc5eac20b4f6e1b271d9df2343d843e1e1fb03c4cbb673f2872d459ce6f01" }, - "3": { + { + "identifier": 3, "participant_share": "f17e505f0e2581c6acfe54d3846a622834b5e7b50cad9a2109a97ba7a80d5c04" } - } + ] }, "round_one_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "hiding_nonce_randomness": "81800157bb554f299fe0b6bd658e4c4591d74168b5177bf55e8dceed59dc80c7", - "binding_nonce_randomness": "e9b37de02fde28f601f09051ed9a277b02ac81c803a5c72492d58635001fe355", - "hiding_nonce": "40f58e8df202b21c94f826e76e4647efdb0ea3ca7ae7e3689bc0cbe2e2f6660c", - "binding_nonce": "373dd42b5fe80e88edddf82e03744b6a12d59256f546de612d4bbd91a6b1df06", - "hiding_nonce_commitment": "b8c7319a56b296537436e5a6f509a871a3c74eff1534ec1e2f539ccd8b322411", - "binding_nonce_commitment": "7af5d4bece8763ce3630370adbd978699402f624fd3a7d2c71ea5839efc3cf54", - "binding_factor_input": "9c245d5fc2e451c5c5a617cc6f2a20629fb317d9b1c1915ab4bfa319d4ebf922c54dd1a5b3b754550c72734ac9255db8107a2b01f361754d9f13f428c2f6de9e4f609ae0dbe8bd1f95bee9f9ea219154d567ef174390bac737bb67ee1787c8a34279728d4aa99a6de2d5ce6deb86afe6bc68178f01223bb5eb934c8a23b6354e0100000000000000000000000000000000000000000000000000000000000000", - "binding_factor": "607df5e2e3a8b5e2704716693e18f548100a32b86a5685d3932a774c3f107e06" + "outputs": [ + { + "identifier": 1, + "hiding_nonce_randomness": "f595a133b4d95c6e1f79887220c8b275ce6277e7f68a6640e1e7140f9be2fb5c", + "binding_nonce_randomness": "34dd1001360e3513cb37bebfabe7be4a32c5bb91ba19fbd4360d039111f0fbdc", + "hiding_nonce": "214f2cabb86ed71427ea7ad4283b0fae26b6746c801ce824b83ceb2b99278c03", + "binding_nonce": "c9b8f5e16770d15603f744f8694c44e335e8faef00dad182b8d7a34a62552f0c", + "hiding_nonce_commitment": "965def4d0958398391fc06d8c2d72932608b1e6255226de4fb8d972dac15fd57", + "binding_nonce_commitment": "ec5170920660820007ae9e1d363936659ef622f99879898db86e5bf1d5bf2a14", + "binding_factor_input": "e2a62f39eede11269e3bd5a7d97554f5ca384f9f6d3dd9c3c0d05083c7254f572889dde2854e26377a16caf77dfee5f6be8fe5b4c80318da84698a4161021b033911db5ef8205362701bc9ecd983027814abee94f46d094943a2f4b79a6e4d4603e52c435d8344554942a0a472d8ad84320585b8da3ae5b9ce31cd1903f795c1af66de22af1a45f652cd05ee446b1b4091aaccc91e2471cd18a85a659cecd11f0100000000000000000000000000000000000000000000000000000000000000", + "binding_factor": "8967fd70fa06a58e5912603317fa94c77626395a695a0e4e4efc4476662eba0c" }, - "3": { - "hiding_nonce_randomness": "daeb223c4a913943cff2fb0b0e638dfcc281e1e8936ee6c3fef4d49ad9cbfaa0", - "binding_nonce_randomness": "c425768d952ab8f18b9720c54b93e612ba2cca170bb7518cac080896efa7429b", - "hiding_nonce": "491477c9dbe8717c77c6c1e2c5f4cec636c7c154313a44c91fea63e309f3e100", - "binding_nonce": "3ae1bba7d6f2076f81596912dd916efae5b3c2ef896956321194fdd2e52ebc0f", - "hiding_nonce_commitment": "e4466b7670ac4f9d9b7b67655860dd1ab341be18a654bb1966df53c76c85d511", - "binding_nonce_commitment": "ce47cd595d25d7effc3c095efa2a687a1728a5ecab402b39e0c0ad9a525ea54f", - "binding_factor_input": "9c245d5fc2e451c5c5a617cc6f2a20629fb317d9b1c1915ab4bfa319d4ebf922c54dd1a5b3b754550c72734ac9255db8107a2b01f361754d9f13f428c2f6de9e4f609ae0dbe8bd1f95bee9f9ea219154d567ef174390bac737bb67ee1787c8a34279728d4aa99a6de2d5ce6deb86afe6bc68178f01223bb5eb934c8a23b6354e0300000000000000000000000000000000000000000000000000000000000000", - "binding_factor": "2bd27271c28746eb93e2114d6778c12b44c9287d84b85dc780eb08da6f689900" + { + "identifier": 3, + "hiding_nonce_randomness": "daa0cf42a32617786d390e0c7edfbf2efbd428037069357b5173ae61d6dd5d5e", + "binding_nonce_randomness": "b4387e72b2e4108ce4168931cc2c7fcce5f345a5297368952c18b5fc8473f050", + "hiding_nonce": "3f7927872b0f9051dd98dd73eb2b91494173bbe0feb65a3e7e58d3e2318fa40f", + "binding_nonce": "ffd79445fb8030f0a3ddd3861aa4b42b618759282bfe24f1f9304c7009728305", + "hiding_nonce_commitment": "480e06e3de182bf83489c45d7441879932fd7b434a26af41455756264fbd5d6e", + "binding_nonce_commitment": "3064746dfd3c1862ef58fc68c706da287dd925066865ceacc816b3a28c7b363b", + "binding_factor_input": "e2a62f39eede11269e3bd5a7d97554f5ca384f9f6d3dd9c3c0d05083c7254f572889dde2854e26377a16caf77dfee5f6be8fe5b4c80318da84698a4161021b033911db5ef8205362701bc9ecd983027814abee94f46d094943a2f4b79a6e4d4603e52c435d8344554942a0a472d8ad84320585b8da3ae5b9ce31cd1903f795c1af66de22af1a45f652cd05ee446b1b4091aaccc91e2471cd18a85a659cecd11f0300000000000000000000000000000000000000000000000000000000000000", + "binding_factor": "f2c1bb7c33a10511158c2f1766a4a5fadf9f86f2a92692ed333128277cc31006" } - } + ] }, "round_two_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "sig_share": "c38f438c325ce6bfa4272b37e7707caaeb57fa8c7ddcc05e0725acb8a7d9cd0c" + "outputs": [ + { + "identifier": 1, + "sig_share": "9285f875923ce7e0c491a592e9ea1865ec1b823ead4854b48c8a46287749ee09" }, - "3": { - "sig_share": "4cb9917be3bd53f1d60f1c3d1a3ff563565fa15a391133e7f980e55d3aeb7904" + { + "identifier": 3, + "sig_share": "7cb211fe0e3d59d25db6e36b3fb32344794139602a7b24f1ae0dc4e26ad7b908" } - } + ] }, "final_output": { - "sig": "204d5d93aa486192ecf2f64ce7dbc1db76948fb1077d1a719ae1ecca6143501e2275dfaafbb62759a59a4fd122b692f941b79be7b6edf34501a69116e2c44701" + "sig": "fc45655fbc66bbffad654ea4ce5fdae253a49a64ace25d9adb62010dd9fb25552164141787162e5b4cab915b4aa45d94655dbb9ed7c378a53b980a0be220a802" } -} +} \ No newline at end of file diff --git a/tests/vectors/frost-secp256k1-sha256.json b/tests/vectors/frost-secp256k1-sha256.json index e7f8e31..b361db5 100644 --- a/tests/vectors/frost-secp256k1-sha256.json +++ b/tests/vectors/frost-secp256k1-sha256.json @@ -8,61 +8,70 @@ "hash": "SHA-256" }, "inputs": { + "participant_list": [ + 1, + 3 + ], "group_secret_key": "0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114", "group_public_key": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4f", "message": "74657374", "share_polynomial_coefficients": [ "fbf85eadae3058ea14f19148bb72b45e4399c0b16028acaf0395c9b03c823579" ], - "participants": { - "1": { + "participant_shares": [ + { + "identifier": 1, "participant_share": "08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c" }, - "2": { + { + "identifier": 2, "participant_share": "04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d82a385554e6e401984" }, - "3": { + { + "identifier": 3, "participant_share": "00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f5e748c071da8c0dbc" } - } + ] }, "round_one_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "hiding_nonce_randomness": "80cbea5e405d169999d8c4b30b755fedb26ab07ec8198cda4873ed8ce5e16773", - "binding_nonce_randomness": "f6d5b38197843046b68903048c1feba433e3500145281fa8bb1e26fdfeef5e7f", - "hiding_nonce": "acc83278035223c1ba464e2d11bfacfc872b2b23e1041cf5f6130da21e4d8068", - "binding_nonce": "c3ef169995bc3d2c2d48f30b83d0c63751e67ceb057695bcb2a6aa40ed5d926b", - "hiding_nonce_commitment": "036673d68a928793c33ae07776908eae8ea15dd947ed81284e939aaba118573a5e", - "binding_nonce_commitment": "03d2a96dd4ec1ee29dc22067109d1290dabd8016cb41856ee8ff9281c3fa1baffd", - "binding_factor_input": "a645d8249457bbcac34fa7b740f66bcce08fc39506b8bbf1a1c81092f6272eda82ae39234d714f87a7b91dd67d124a06561a36817c1ecaa255c3527d694fc4f10000000000000000000000000000000000000000000000000000000000000001", - "binding_factor": "d7bcbd29408dedc9e138262d99b09d8b5705d76eb5de2369d9103e4423f8ac79" + "outputs": [ + { + "identifier": 1, + "hiding_nonce_randomness": "7ea5ed09af19f6ff21040c07ec2d2adbd35b759da5a401d4c99dd26b82391cb2", + "binding_nonce_randomness": "47acab018f116020c10cb9b9abdc7ac10aae1b48ca6e36dc15acb6ec9be5cdc5", + "hiding_nonce": "841d3a6450d7580b4da83c8e618414d0f024391f2aeb511d7579224420aa81f0", + "binding_nonce": "8d2624f532af631377f33cf44b5ac5f849067cae2eacb88680a31e77c79b5a80", + "hiding_nonce_commitment": "03c699af97d26bb4d3f05232ec5e1938c12f1e6ae97643c8f8f11c9820303f1904", + "binding_nonce_commitment": "02fa2aaccd51b948c9dc1a325d77226e98a5a3fe65fe9ba213761a60123040a45e", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fff9b5210ffbb3c07a73a7c8935be4a8c62cf015f6cf7ade6efac09a6513540fc3f5a816aaebc2114a811a415d7a55db7c5cbc1cf27183e79dd9def941b5d48010000000000000000000000000000000000000000000000000000000000000001", + "binding_factor": "3e08fe561e075c653cbfd46908a10e7637c70c74f0a77d5fd45d1a750c739ec6" }, - "3": { - "hiding_nonce_randomness": "b9794047604beda0c5c0529ac9dfd83c0a80399a7bdf4c3e23cef2faf69cdcc3", - "binding_nonce_randomness": "c28ce6252631620b84c2702b34774fab365e286ebc77030a112ebccccbffa78b", - "hiding_nonce": "cb3387defef07fc9010c0564ba6495ed41876626ed86b886ca26cbbd3566ffbc", - "binding_nonce": "4559459735eb68e8c16319a9fd9a14016053957cb8cea273a24b7c7bc1ee26f6", - "hiding_nonce_commitment": "030278e6e6055fb963b40e0c3c37099f803f3f38930fc89092517f8ce1b47e8d6b", - "binding_nonce_commitment": "028eb6d238c6c0fc6216906706ad0ff9943c6c1d6079cdf74f674481ebb2485db3", - "binding_factor_input": "a645d8249457bbcac34fa7b740f66bcce08fc39506b8bbf1a1c81092f6272eda82ae39234d714f87a7b91dd67d124a06561a36817c1ecaa255c3527d694fc4f10000000000000000000000000000000000000000000000000000000000000003", - "binding_factor": "ecc057259f3c8b195308c9b73aaaf840660a37eb264ebce342412c58102ee437" + { + "identifier": 3, + "hiding_nonce_randomness": "e6cc56ccbd0502b3f6f831d91e2ebd01c4de0479e0191b66895a4ffd9b68d544", + "binding_nonce_randomness": "7203d55eb82a5ca0d7d83674541ab55f6e76f1b85391d2c13706a89a064fd5b9", + "hiding_nonce": "2b19b13f193f4ce83a399362a90cdc1e0ddcd83e57089a7af0bdca71d47869b2", + "binding_nonce": "7a443bde83dc63ef52dda354005225ba0e553243402a4705ce28ffaafe0f5b98", + "hiding_nonce_commitment": "03077507ba327fc074d2793955ef3410ee3f03b82b4cdc2370f71d865beb926ef6", + "binding_nonce_commitment": "02ad53031ddfbbacfc5fbda3d3b0c2445c8e3e99cbc4ca2db2aa283fa68525b135", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fff9b5210ffbb3c07a73a7c8935be4a8c62cf015f6cf7ade6efac09a6513540fc3f5a816aaebc2114a811a415d7a55db7c5cbc1cf27183e79dd9def941b5d48010000000000000000000000000000000000000000000000000000000000000003", + "binding_factor": "93f79041bb3fd266105be251adaeb5fd7f8b104fb554a4ba9a0becea48ddbfd7" } - } + ] }, "round_two_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "sig_share": "1750b2a314a81b66fd81366583617aaafcffa68f14495204795aa0434b907aa3" + "outputs": [ + { + "identifier": 1, + "sig_share": "c4fce1775a1e141fb579944166eab0d65eefe7b98d480a569bbbfcb14f91c197" }, - "3": { - "sig_share": "e4dbbbbbcb035eb3512918b0368c4ab2c836a92dccff3251efa7a4aacc7d3790" + { + "identifier": 3, + "sig_share": "0160fd0d388932f4826d2ebcd6b9eaba734f7c71cf25b4279a4ca2581e47b18d" } - } + ] }, "final_output": { - "sig": "0259696aac722558e8638485d252bb2556f6241a7adfdf284c8c87a3428d46448dfc2c6e5edfab7a1a4eaa4f15b9edc55dc5364fbce1488456690244ee180db233" + "sig": "0205b6d04d3774c8929413e3c76024d54149c372d57aae62574ed74319b5ea14d0c65dde8492a7471437e6c2fe3da49b90d23f642b5c6dbe7e36089f096dd97324" } } \ No newline at end of file diff --git a/tests/vectors_test.go b/tests/vectors_test.go index 17ef2c8..76b3d5f 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -10,18 +10,15 @@ package frost_test import ( "bytes" - "encoding/hex" "encoding/json" "fmt" "os" "path/filepath" "testing" - group "github.com/bytemare/crypto" secretsharing "github.com/bytemare/secret-sharing" "github.com/bytemare/frost" - "github.com/bytemare/frost/internal" ) func (v test) test(t *testing.T) { @@ -40,7 +37,8 @@ func (v test) test(t *testing.T) { } if len(secretsharingCommitment) != v.Config.MinParticipants { - t.Fatalf("%d / %d", len(secretsharingCommitment), v.Config.MinParticipants) + t.Fatalf( + "%d / %d", len(secretsharingCommitment), v.Config.MinParticipants) } // Check whether key shares are the same @@ -57,6 +55,7 @@ func (v test) test(t *testing.T) { t.Fatal("Some key shares do not match.") } + // Test recovery of the full secret signing key. recoveredKey, err := secretsharing.Combine(g, uint(v.Config.MinParticipants), privateKeyShares) if err != nil { t.Fatal(err) @@ -76,36 +75,30 @@ func (v test) test(t *testing.T) { } for i, shareI := range privateKeyShares { - if !frost.Verify(g, shareI, secretsharingCommitment) { + if !frost.VerifyVSS(g, shareI, secretsharingCommitment) { t.Fatal(i) } } // Create participants participants := make(ParticipantList, len(privateKeyShares)) + conf := v.Config + conf.GroupPublicKey = groupPublicKey for i, pks := range privateKeyShares { - participants[i] = &frost.Participant{ - ParticipantInfo: frost.ParticipantInfo{ - KeyShare: pks, - Lambda: nil, - }, - Nonce: [2]*group.Scalar{}, - Configuration: *v.Config.Configuration, - } - participants[i].Configuration.GroupPublicKey = groupPublicKey + participants[i] = conf.Participant(pks.Identifier, pks.SecretKey) } // Round One: Commitment - commitmentList := make(internal.CommitmentList, len(v.RoundOneOutputs.ParticipantList)) - for i, pid := range v.RoundOneOutputs.ParticipantList { - p := participants.Get(pid) + commitmentList := make(frost.CommitmentList, len(v.RoundOneOutputs.Outputs)) + for i, pid := range v.RoundOneOutputs.Outputs { + p := participants.Get(pid.ID) if p == nil { t.Fatal(i) } var pv *participant - for _, pp := range v.RoundOneOutputs.Participants { - if pp.ID.Equal(pid) == 1 { + for _, pp := range v.RoundOneOutputs.Outputs { + if pp.ID.Equal(pid.ID) == 1 { pv = pp } } @@ -119,11 +112,7 @@ func (v test) test(t *testing.T) { commitment := p.Commit() if p.Nonce[0].Equal(pv.HidingNonce) != 1 { - t.Fatalf( - "invalid value\nwant: %v\ngot : %v\n", - hex.EncodeToString(pv.HidingNonce.Encode()), - hex.EncodeToString(p.Nonce[0].Encode()), - ) + t.Fatal(i) } if p.Nonce[1].Equal(pv.BindingNonce) != 1 { t.Fatal(i) @@ -138,20 +127,20 @@ func (v test) test(t *testing.T) { commitmentList[i] = commitment } - _, rhoInputs := commitmentList.ComputeBindingFactors( - v.Config.Ciphersuite, - v.Inputs.Message, - ) - for i, rho := range rhoInputs { - if !bytes.Equal(rho, v.RoundOneOutputs.Participants[i].BindingFactorInput) { - t.Fatal() - } - } + //_, rhoInputs := commitmentList.ComputeBindingFactors( + // v.Config.Ciphersuite, + // v.Inputs.Message, + //) + //for i, rho := range rhoInputs { + // if !bytes.Equal(rho, v.RoundOneOutputs.Outputs[i].BindingFactorInput) { + // t.Fatal() + // } + //} // Round two: sign - sigShares := make([]*group.Scalar, len(v.RoundTwoOutputs.ParticipantList)) - for i, pid := range v.RoundTwoOutputs.ParticipantList { - p := participants.Get(pid) + sigShares := make([]*frost.SignatureShare, len(v.RoundTwoOutputs.Outputs)) + for i, pid := range v.RoundTwoOutputs.Outputs { + p := participants.Get(pid.Identifier) if p == nil { t.Fatal(i) } @@ -162,8 +151,8 @@ func (v test) test(t *testing.T) { } } - for i, ks := range v.RoundTwoOutputs.Participants { - if ks.SecretKey.Equal(sigShares[i]) != 1 { + for i, ks := range v.RoundTwoOutputs.Outputs { + if ks.SecretKey.Equal(sigShares[i].SignatureShare) != 1 { t.Fatal(i) } } @@ -173,6 +162,11 @@ func (v test) test(t *testing.T) { if !bytes.Equal(sig.Encode(), v.FinalOutput) { t.Fatal() } + + // Sanity Check + if !frost.Verify(conf.Ciphersuite, v.Inputs.Message, sig, groupPublicKey) { + t.Fatal() + } } func loadFrostVectors(t *testing.T, filepath string) (*test, error) { @@ -210,7 +204,7 @@ func TestFrostVectors(t *testing.T) { t.Fatal(err) } - t.Run(fmt.Sprintf("%s - %s", v.Config.Name, v.Config.Ciphersuite.Group), v.test) + t.Run(fmt.Sprintf("%s", v.Config.Name), v.test) return nil }); err != nil {