Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,24 @@ When you build a transport, it should offer a broadcast channel as well as point

Within your transport, each message should be wrapped with a **session ID** that is unique to a single run of the keygen or signing rounds. This session ID should be agreed upon out-of-band and known only by the participating parties before the rounds begin. Upon receiving any message, your program should make sure that the received session ID matches the one that was agreed upon at the start.

The same session ID should be bound into the protocol parameters before constructing local parties:
The proof-transcript mode must be selected explicitly before constructing an
ECDSA keygen or signing party. New ceremonies should use the session-bound
security-v2 mode and bind the same session ID into the protocol parameters:

```go
params := tss.NewParameters(curve, ctx, thisParty, len(parties), threshold)
params.SetProtocolMode(tss.ProtocolModeSecurityV2)
params.SetSessionNonceBytes([]byte(sessionID))
```

All parties in the run must use the same high-entropy session ID of at least 16 bytes, and it must be unique to the ceremony. Keygen and signing fail closed if no session nonce is set; reusing a session ID across otherwise identical ceremonies reintroduces transcript-splicing risk.

`ProtocolModeLegacy` exists only for coordinated compatibility with peers that
use the historical untagged GG20 transcript. A ceremony must be homogeneous:
legacy and security-v2 parties cannot interoperate. The selected mode is frozen
when the local party is constructed and cannot change while the protocol is in
flight.

Additionally, there should be a mechanism in your transport to allow for "reliable broadcasts", meaning parties can broadcast a message to other parties such that it's guaranteed that each one receives the same message. There are several examples of algorithms online that do this by sharing and comparing hashes of received messages.

Timeouts and errors should be handled by your application. The method `WaitingFor` may be called on a `Party` to get the set of other parties that it is still waiting for messages from. You may also get the set of culprit parties that caused an error from a `*tss.Error`.
Expand Down
11 changes: 9 additions & 2 deletions crypto/dlnproof/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func NewDLNProof(h1, h2, x, p, q, N *big.Int, session ...[]byte) *Proof {
alpha[i] = modN.Exp(h1, a[i])
}
msg := append([]*big.Int{h1, h2, N}, alpha[:]...)
c := common.SHA512_256i_TAGGED(fsSessionDLNProof(Session), msg...)
c := proofChallenge(Session, msg...)
t := [Iterations]*big.Int{}
cIBI := new(big.Int)
for i := range t {
Expand Down Expand Up @@ -87,7 +87,7 @@ func (p *Proof) Verify(h1, h2, N *big.Int, session ...[]byte) bool {
}
}
msg := append([]*big.Int{h1, h2, N}, p.Alpha[:]...)
c := common.SHA512_256i_TAGGED(fsSessionDLNProof(Session), msg...)
c := proofChallenge(Session, msg...)
cIBI := new(big.Int)
for i := 0; i < Iterations; i++ {
cI := c.Bit(i)
Expand All @@ -102,6 +102,13 @@ func (p *Proof) Verify(h1, h2, N *big.Int, session ...[]byte) bool {
return true
}

func proofChallenge(session []byte, values ...*big.Int) *big.Int {
if session == nil {
return common.SHA512_256i(values...)
}
return common.SHA512_256i_TAGGED(fsSessionDLNProof(session), values...)
}

func optionalSession(session [][]byte) []byte {
if len(session) == 0 {
return nil
Expand Down
17 changes: 17 additions & 0 deletions crypto/dlnproof/proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,25 @@ package dlnproof
import (
"math/big"
"testing"

"github.com/bnb-chain/tss-lib/common"
)

func TestLegacyChallengeMatchesHistoricalTranscript(t *testing.T) {
values := []*big.Int{
big.NewInt(2),
big.NewInt(3),
big.NewInt(5),
big.NewInt(7),
}

expected := common.SHA512_256i(values...)
actual := proofChallenge(nil, values...)
if expected.Cmp(actual) != 0 {
t.Fatalf("legacy challenge changed: expected %v, got %v", expected, actual)
}
}

func TestDLNProofRejectsEmptySessionTag(t *testing.T) {
assertPanics(t, func() {
_ = NewDLNProof(nil, nil, nil, nil, nil, nil, []byte{})
Expand Down
143 changes: 117 additions & 26 deletions crypto/mta/proofs.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,23 @@ func ProveBobWC(ec elliptic.Curve, pk *paillier.PublicKey, NTilde, h1, h2, c1, c
w = modNTilde.Mul(w, modNTilde.Exp(h2, tau))

// 11-12. e'
var e *big.Int
{ // derive the Fiat-Shamir challenge by reducing the hash mod q
var eHash *big.Int
// X is nil if called by ProveBob (Bob's proof "without check")
if X == nil {
eHash = common.SHA512_256i_TAGGED(fsSessionBob(Session), append(pk.AsInts(), NTilde, h1, h2, c1, c2, z, zPrm, t, v, w)...)
} else {
eHash = common.SHA512_256i_TAGGED(fsSessionBobWC(Session), append(pk.AsInts(), NTilde, h1, h2, X.X(), X.Y(), c1, c2, u.X(), u.Y(), z, zPrm, t, v, w)...)
}
e = common.ModReduceHash(q, eHash)
}
e := bobProofChallenge(
Session,
q,
pk,
NTilde,
h1,
h2,
c1,
c2,
X,
u,
z,
zPrm,
t,
v,
w,
)

// 13.
modN := common.ModInt(pk.N)
Expand Down Expand Up @@ -292,23 +298,31 @@ func (pf *ProofBobWC) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTilde,
}

// 1-2. e'
var e *big.Int
{ // derive the Fiat-Shamir challenge by reducing the hash mod q
var eHash *big.Int
// X is nil if called on a ProveBob (Bob's proof "without check")
if X == nil {
eHash = common.SHA512_256i_TAGGED(fsSessionBob(Session), append(pk.AsInts(), NTilde, h1, h2, c1, c2, pf.Z, pf.ZPrm, pf.T, pf.V, pf.W)...)
} else {
if !X.ValidateBasic() || !crypto.SameCurve(ec, X.Curve()) {
return false
}
if !pf.U.ValidateBasic() || !crypto.SameCurve(ec, pf.U.Curve()) {
return false
}
eHash = common.SHA512_256i_TAGGED(fsSessionBobWC(Session), append(pk.AsInts(), NTilde, h1, h2, X.X(), X.Y(), c1, c2, pf.U.X(), pf.U.Y(), pf.Z, pf.ZPrm, pf.T, pf.V, pf.W)...)
if X != nil {
if !X.ValidateBasic() || !crypto.SameCurve(ec, X.Curve()) {
return false
}
if !pf.U.ValidateBasic() || !crypto.SameCurve(ec, pf.U.Curve()) {
return false
}
e = common.ModReduceHash(q, eHash)
}
e := bobProofChallenge(
Session,
q,
pk,
NTilde,
h1,
h2,
c1,
c2,
X,
pf.U,
pf.Z,
pf.ZPrm,
pf.T,
pf.V,
pf.W,
)
if e.Sign() == 0 {
return false
}
Expand Down Expand Up @@ -372,6 +386,83 @@ func (pf *ProofBobWC) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTilde,
return true
}

func bobProofChallenge(
session []byte,
q *big.Int,
pk *paillier.PublicKey,
nTilde, h1, h2, c1, c2 *big.Int,
x, u *crypto.ECPoint,
z, zPrime, t, v, w *big.Int,
) *big.Int {
if session == nil {
if x == nil {
return common.HashToN(
q,
append(pk.AsInts(), c1, c2, z, zPrime, t, v, w)...,
)
}
return common.HashToN(
q,
append(
pk.AsInts(),
x.X(),
x.Y(),
c1,
c2,
u.X(),
u.Y(),
z,
zPrime,
t,
v,
w,
)...,
)
}

if x == nil {
challengeHash := common.SHA512_256i_TAGGED(
fsSessionBob(session),
append(
pk.AsInts(),
nTilde,
h1,
h2,
c1,
c2,
z,
zPrime,
t,
v,
w,
)...,
)
return common.ModReduceHash(q, challengeHash)
}

challengeHash := common.SHA512_256i_TAGGED(
fsSessionBobWC(session),
append(
pk.AsInts(),
nTilde,
h1,
h2,
x.X(),
x.Y(),
c1,
c2,
u.X(),
u.Y(),
z,
zPrime,
t,
v,
w,
)...,
)
return common.ModReduceHash(q, challengeHash)
}

// ProveBob.Verify implements verification of Bob's proof without check "VerifyMta_Bob" used in the MtA protocol from GG18Spec (9) Fig. 11.
func (pf *ProofBob) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTilde, h1, h2, c1, c2 *big.Int, session ...[]byte) bool {
if pf == nil {
Expand Down
36 changes: 32 additions & 4 deletions crypto/mta/range_proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ func ProveRangeAlice(ec elliptic.Curve, pk *paillier.PublicKey, c, NTilde, h1, h
w = modNTilde.Mul(w, modNTilde.Exp(h2, gamma))

// 8-9. e'
eHash := common.SHA512_256i_TAGGED(fsSessionRangeAlice(Session), append(pk.AsInts(), NTilde, h1, h2, c, z, u, w)...)
e := common.ModReduceHash(q, eHash)
e := rangeProofChallenge(Session, q, pk, NTilde, h1, h2, c, z, u, w)

modN := common.ModInt(pk.N)
s := modN.Exp(r, e)
Expand Down Expand Up @@ -196,8 +195,18 @@ func (pf *RangeProofAlice) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTi
}

// 1-2. e'
eHash := common.SHA512_256i_TAGGED(fsSessionRangeAlice(Session), append(pk.AsInts(), NTilde, h1, h2, c, pf.Z, pf.U, pf.W)...)
e := common.ModReduceHash(q, eHash)
e := rangeProofChallenge(
Session,
q,
pk,
NTilde,
h1,
h2,
c,
pf.Z,
pf.U,
pf.W,
)
if e.Sign() == 0 {
return false
}
Expand Down Expand Up @@ -235,6 +244,25 @@ func (pf *RangeProofAlice) Verify(ec elliptic.Curve, pk *paillier.PublicKey, NTi
return true
}

func rangeProofChallenge(
session []byte,
q *big.Int,
pk *paillier.PublicKey,
nTilde, h1, h2, c, z, u, w *big.Int,
) *big.Int {
if session == nil {
// Historical GG20 transcript. The auxiliary modulus and generators
// were not included in the challenge input.
return common.HashToN(q, append(pk.AsInts(), c, z, u, w)...)
}

challengeHash := common.SHA512_256i_TAGGED(
fsSessionRangeAlice(session),
append(pk.AsInts(), nTilde, h1, h2, c, z, u, w)...,
)
return common.ModReduceHash(q, challengeHash)
}

func (pf *RangeProofAlice) ValidateBasic() bool {
return pf.Z != nil &&
pf.U != nil &&
Expand Down
25 changes: 25 additions & 0 deletions crypto/mta/range_proof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,31 @@ func TestProofSessionRejectsEmptyTag(t *testing.T) {
})
}

func TestLegacyRangeChallengeMatchesHistoricalTranscript(t *testing.T) {
q := tss.EC().Params().N
pk := &paillier.PublicKey{N: big.NewInt(17)}
c := big.NewInt(19)
z := big.NewInt(23)
u := big.NewInt(29)
w := big.NewInt(31)

expected := common.HashToN(q, append(pk.AsInts(), c, z, u, w)...)
actual := rangeProofChallenge(
nil,
q,
pk,
big.NewInt(37),
big.NewInt(41),
big.NewInt(43),
c,
z,
u,
w,
)

assert.Equal(t, 0, expected.Cmp(actual))
}

func TestProveRangeAlice(t *testing.T) {
q := tss.EC().Params().N

Expand Down
Loading
Loading