Skip to content

Commit

Permalink
Merge pull request #4 from threshold-network/frost-1
Browse files Browse the repository at this point in the history
Ciphersuite and FROST round one
  • Loading branch information
eth-r authored Dec 15, 2023
2 parents 5541497 + 98d2420 commit 40f49d7
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 57 deletions.
66 changes: 39 additions & 27 deletions roast/hash.go → frost/bip340.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,45 @@
package roast
package frost

import (
"crypto/sha256"
"math/big"

"github.com/ethereum/go-ethereum/crypto/secp256k1"
)

// Hash interface abstracts out hash functions implementations specific to the
// ciphersuite used. This is a strategy design pattern allowing to use FROST
// with different ciphersuites, like secp256k1 or Jubjub curves.
//
// [FROST] requires the use of a cryptographically secure hash function,
// generically written as H. Using H, [FROST] introduces distinct domain-separated
// hashes, H1, H2, H3, H4, and H5. The details of H1, H2, H3, H4, and H5 vary
// based on ciphersuite.
type Hash interface {
H1(m []byte) *big.Int
H2(m []byte, ms ...[]byte) *big.Int
H3(m []byte, ms ...[]byte) *big.Int
H4(m []byte) []byte
H5(m []byte) []byte
// Bip340Ciphersuite is [BIP-340] implementation of [FROST] ciphersuite.
// The ciphersuite uses secp256k1 elliptic curve as the prime-order group and
// Bitcoin hashing function implementation for H* [FROST] functions.
type Bip340Ciphersuite struct {
curve *Bip340Curve
}

// NewBip340Ciphersuite creates a new instance of Bip340Ciphersuite in a state
// ready to be used for the [FROST] protocol execution.
func NewBip340Ciphersuite() *Bip340Ciphersuite {
return &Bip340Ciphersuite{
curve: &Bip340Curve{secp256k1.S256()},
}
}

// Curve returns secp256k1 curve implementation used in [BIP-340].
func (b *Bip340Ciphersuite) Curve() Curve {
return b.curve
}

type Bip340Curve struct {
*secp256k1.BitCurve
}

// Bip340Hash is [BIP-340] implementation of [FROST] functions required by the
// `Hash` interface.
type Bip340Hash struct {
// EcBaseMul returns k*G, where G is the base point of the group.
func (bc *Bip340Curve) EcBaseMul(k *big.Int) *Point {
sp := new(big.Int).Mod(k, bc.N)
gs_x, gs_y := bc.ScalarBaseMult(sp.Bytes())
return &Point{gs_x, gs_y}
}

// H1 is the implementation of H1(m) function from [FROST].
func (b *Bip340Hash) H1(m []byte) *big.Int {
func (b *Bip340Ciphersuite) H1(m []byte) *big.Int {
// From [FROST], we know the tag should be DST = contextString || "rho".
dst := concat(b.contextString(), []byte("rho"))
// We use [BIP-340]-compatible hashing algorithm and turn the hash into
Expand All @@ -36,7 +48,7 @@ func (b *Bip340Hash) H1(m []byte) *big.Int {
}

// H2 is the implementation of H2(m) function from [FROST].
func (b *Bip340Hash) H2(m []byte, ms ...[]byte) *big.Int {
func (b *Bip340Ciphersuite) H2(m []byte, ms ...[]byte) *big.Int {
// For H2, we need to use [BIP-340] tag because the verification algorithm
// from [BIP034] expects this tag to be used:
//
Expand All @@ -47,7 +59,7 @@ func (b *Bip340Hash) H2(m []byte, ms ...[]byte) *big.Int {
}

// H3 is the implementation of H3(m) function from [FROST].
func (b *Bip340Hash) H3(m []byte, ms ...[]byte) *big.Int {
func (b *Bip340Ciphersuite) H3(m []byte, ms ...[]byte) *big.Int {
// From [FROST], we know the tag should be DST = contextString || "nonce".
dst := concat(b.contextString(), []byte("nonce"))
// We use [BIP-340]-compatible hashing algorithm and turn the hash into
Expand All @@ -56,15 +68,15 @@ func (b *Bip340Hash) H3(m []byte, ms ...[]byte) *big.Int {
}

// H4 is the implementation of H4(m) function from [FROST].
func (b *Bip340Hash) H4(m []byte, ms ...[]byte) []byte {
func (b *Bip340Ciphersuite) H4(m []byte, ms ...[]byte) []byte {
// From [FROST], we know the tag should be DST = contextString || "msg".
dst := concat(b.contextString(), []byte("msg"))
hash := b.hash(dst, m)
return hash[:]
}

// H5 is the implementation of H5(m) function from [FROST].
func (b *Bip340Hash) H5(m []byte, ms ...[]byte) []byte {
func (b *Bip340Ciphersuite) H5(m []byte, ms ...[]byte) []byte {
// From [FROST], we know the tag should be DST = contextString || "com".
dst := concat(b.contextString(), []byte("com"))
hash := b.hash(dst, m)
Expand All @@ -73,7 +85,7 @@ func (b *Bip340Hash) H5(m []byte, ms ...[]byte) []byte {

// contextString is a contextString as required by [FROST] to be used in tagged
// hashes. The value is specific to [BIP-340] ciphersuite.
func (b *Bip340Hash) contextString() []byte {
func (b *Bip340Ciphersuite) contextString() []byte {
// The contextString as defined in section 6.5. FROST(secp256k1, SHA-256) of
// [FROST] is "FROST-secp256k1-SHA256-v1". Since we do a BIP-340 specialized
// version, we use "FROST-secp256k1-BIP340-v1".
Expand All @@ -82,7 +94,7 @@ func (b *Bip340Hash) contextString() []byte {

// hashToScalar computes [BIP-340] tagged hash of the message and turns it into
// a scalar modulo secp256k1 curve order, as specified in [BIP-340].
func (b *Bip340Hash) hashToScalar(tag, msg []byte) *big.Int {
func (b *Bip340Ciphersuite) hashToScalar(tag, msg []byte) *big.Int {
hashed := b.hash(tag, msg)
ej := os2ip(hashed[:])

Expand All @@ -92,13 +104,13 @@ func (b *Bip340Hash) hashToScalar(tag, msg []byte) *big.Int {
// the curve order will produce an unacceptably biased result. However, for
// the secp256k1 curve, the order is sufficiently close to 2256 that this
// bias is not observable (1 - n / 2^256 is around 1.27 * 2^-128).
ej.Mod(ej, G.N)
ej.Mod(ej, b.curve.N)

return ej
}

// hash implements the tagged hash function as defined in [BIP-340].
func (b *Bip340Hash) hash(tag, msg []byte) [32]byte {
func (b *Bip340Ciphersuite) hash(tag, msg []byte) [32]byte {
// From [BIP-340] specification section:
//
// The function hash_name(x) where x is a byte array returns the 32-byte hash
Expand Down
44 changes: 22 additions & 22 deletions roast/hash_test.go → frost/bip340_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package roast
package frost

import (
"bytes"
Expand All @@ -8,7 +8,7 @@ import (
"threshold.network/roast/internal/testutils"
)

func Test_Bip340Hash_H1(t *testing.T) {
func Test_Bip340Ciphersuite_H1(t *testing.T) {
// There are no official test vectors available. Yet, we want to ensure the
// function does not panic for empty or nil. We also want to make sure the
// happy path works producing a non-zero value.
Expand All @@ -27,15 +27,15 @@ func Test_Bip340Hash_H1(t *testing.T) {
},
}

bip340Hash := new(Bip340Hash)
ciphersuite := NewBip340Ciphersuite()
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
testutils.AssertBigIntNonZero(t, "H1 result", bip340Hash.H1(test.m))
testutils.AssertBigIntNonZero(t, "H1 result", ciphersuite.H1(test.m))
})
}
}

func Test_Bip340Hash_H2(t *testing.T) {
func Test_Bip340Ciphersuite_H2(t *testing.T) {
// There are no official test vectors available. Yet, we want to ensure the
// function does not panic for empty or nil. We also want to make sure the
// happy path works producing a non-zero value.
Expand Down Expand Up @@ -66,16 +66,16 @@ func Test_Bip340Hash_H2(t *testing.T) {
},
}

bip340Hash := new(Bip340Hash)
ciphersuite := NewBip340Ciphersuite()
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
testutils.AssertBigIntNonZero(t, "H2 result", bip340Hash.H2(test.m))
testutils.AssertBigIntNonZero(t, "H2 result", ciphersuite.H2(test.m))

})
}
}

func Test_Bip340Hash_H3(t *testing.T) {
func Test_Bip340Ciphersuite_H3(t *testing.T) {
// There are no official test vectors available. Yet, we want to ensure the
// function does not panic for empty or nil. We also want to make sure the
// happy path works producing a non-zero value.
Expand Down Expand Up @@ -106,15 +106,15 @@ func Test_Bip340Hash_H3(t *testing.T) {
},
}

bip340Hash := new(Bip340Hash)
ciphersuite := NewBip340Ciphersuite()
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
testutils.AssertBigIntNonZero(t, "H3 result", bip340Hash.H3(test.m))
testutils.AssertBigIntNonZero(t, "H3 result", ciphersuite.H3(test.m))
})
}
}

func Test_Bip340Hash_H4(t *testing.T) {
func Test_Bip340Ciphersuite_H4(t *testing.T) {
// There are no official test vectors available. Yet, we want to ensure the
// function does not panic for empty or nil. We also want to make sure the
// happy path works producing a non-zero value.
Expand All @@ -133,18 +133,18 @@ func Test_Bip340Hash_H4(t *testing.T) {
},
}

bip340Hash := new(Bip340Hash)
ciphersuite := NewBip340Ciphersuite()
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
// The length is unknown so we can't use bytes.Equal. Casting to
// big.Int instead.
result := new(big.Int).SetBytes(bip340Hash.H4(test.m))
result := new(big.Int).SetBytes(ciphersuite.H4(test.m))
testutils.AssertBigIntNonZero(t, "H4 result", result)
})
}
}

func Test_Bip340Hash_H5(t *testing.T) {
func Test_Bip340Ciphersuite_H5(t *testing.T) {
// There are no official test vectors available. Yet, we want to ensure the
// function does not panic for empty or nil. We also want to make sure the
// happy path works producing a non-zero value.
Expand All @@ -163,18 +163,18 @@ func Test_Bip340Hash_H5(t *testing.T) {
},
}

bip340Hash := new(Bip340Hash)
ciphersuite := NewBip340Ciphersuite()
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
// The length is unknown so we can't use bytes.Equal. Casting to
// big.Int instead.
result := new(big.Int).SetBytes(bip340Hash.H5(test.m))
result := new(big.Int).SetBytes(ciphersuite.H5(test.m))
testutils.AssertBigIntNonZero(t, "H5 result", result)
})
}
}

func Test_Bip340Hash_hashToScalar(t *testing.T) {
func Test_Bip340Ciphersuite_hashToScalar(t *testing.T) {
var tests = map[string]struct {
tag []byte
msg []byte
Expand Down Expand Up @@ -209,12 +209,12 @@ func Test_Bip340Hash_hashToScalar(t *testing.T) {
},
}

bip340Hash := new(Bip340Hash)
ciphersuite := NewBip340Ciphersuite()
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
// No official test vectors are available so we only make sure the
// function does not panic and returns non-nil and non-zero value.
scalar := bip340Hash.hashToScalar(test.tag, test.msg)
scalar := ciphersuite.hashToScalar(test.tag, test.msg)
if scalar == nil {
t.Fatal("unexpected nil returned")
}
Expand All @@ -223,7 +223,7 @@ func Test_Bip340Hash_hashToScalar(t *testing.T) {
}
}

func Test_Bip340Hash_hash(t *testing.T) {
func Test_Bip340Ciphersuite_hash(t *testing.T) {
var tests = map[string]struct {
tag []byte
msg []byte
Expand Down Expand Up @@ -259,13 +259,13 @@ func Test_Bip340Hash_hash(t *testing.T) {
}

empty := make([]byte, 32)
bip340Hash := new(Bip340Hash)
ciphersuite := NewBip340Ciphersuite()

for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
// No official test vectors are available so we only make sure the
// function does not panic and returns non-zero value
hash := bip340Hash.hash(test.tag, test.msg)
hash := ciphersuite.hash(test.tag, test.msg)
if bytes.Equal(hash[:], empty) {
t.Fatal("empty bytes")
}
Expand Down
40 changes: 40 additions & 0 deletions frost/ciphersuite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package frost

import "math/big"

// Ciphersuite interface abstracts out the particular ciphersuite implementation
// used for the [FROST] protocol execution. This is a strategy design pattern
// allowing to use [FROST] with different ciphersuites, like BIP-340 (secp256k1)
// or Jubjub. A [FROST] ciphersuite must specify the underlying prime-order group
// details and cryptographic hash functions.
type Ciphersuite interface {
Hashing
Curve() Curve
}

// Hashing interface abstracts out hash functions implementations specific to the
// ciphersuite used.
//
// [FROST] requires the use of a cryptographically secure hash function,
// generically written as H. Using H, [FROST] introduces distinct domain-separated
// hashes, H1, H2, H3, H4, and H5. The details of H1, H2, H3, H4, and H5 vary
// based on ciphersuite.
type Hashing interface {
H1(m []byte) *big.Int
H2(m []byte, ms ...[]byte) *big.Int
H3(m []byte, ms ...[]byte) *big.Int
H4(m []byte) []byte
H5(m []byte) []byte
}

// Curve interface abstracts out the particular elliptic curve implementation
// specific to the ciphersuite used.
type Curve interface {
EcBaseMul(*big.Int) *Point
}

// Point represents a valid point on the Curve.
type Point struct {
X *big.Int // the X coordinate of the point
Y *big.Int // the Y coordinate of the point
}
1 change: 1 addition & 0 deletions frost/frost.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package frost
Loading

0 comments on commit 40f49d7

Please sign in to comment.