diff --git a/bip340.go b/bip340.go deleted file mode 100644 index 07093f5..0000000 --- a/bip340.go +++ /dev/null @@ -1,173 +0,0 @@ -package main - -import ( - "fmt" - "math/big" -) - -type BIP340Signature struct { - rb [32]byte - sb [32]byte -} - -func LiftX(x *big.Int) (Point, error) { - p := G.P - if x.Cmp(p) != -1 { - return Point{nil, nil}, fmt.Errorf("LiftX: value of x exceeds field size") - } - c := new(big.Int).Exp(x, big.NewInt(3), p) - c.Add(c, big.NewInt(7)) - c.Mod(c, p) - - e := new(big.Int).Add(p, big.NewInt(1)) - e.Div(e, big.NewInt(4)) - // e.Mod(e, p) - - y := new(big.Int).Exp(c, e, p) - - y2 := new(big.Int).Exp(y, big.NewInt(2), p) - - if c.Cmp(y2) != 0 { - return Point{nil, nil}, fmt.Errorf("LiftX: no curve point matching x") - } - - if y.Bit(0) != 0 { - y.Sub(p, y) - } - return Point{x, y}, nil -} - -// Input: -// The secret key sk: a 32-byte array -// The message m: a byte array -// Auxiliary random data a: a byte array -func BIP340Sign(skb [32]byte, msg []byte, a []byte) BIP340Signature { - // Let d' = int(sk) - dd := FromBytes32(skb) - // Fail if d' = 0 or d' ≥ n - if dd.Cmp(G.N) != -1 { - panic("secret key exceeds range") - } - // Let P = d'⋅G - P := EcBaseMul(dd) - // Let d = d' if has_even_y(P), otherwise let d = n - d'. - d := new(big.Int) - if HasEvenY(P) { - d.Set(dd) - } else { - d.Sub(G.N, dd) - } - // Let t be the byte-wise xor of bytes(d) and hashBIP0340/aux(a)[11]. - auxHash := BIP340HashAux(a) - db := ToBytes32(d) - t := xor(db[:], auxHash[:]) - // Let rand = hashBIP0340/nonce(t || bytes(P) || m)[12]. - pb := P.ToBytes32() - rand := BIP340HashNonce(t, pb[:], msg) - // Let k' = int(rand) mod n[13]. - kk := new(big.Int).Mod(FromBytes32(rand), G.N) - // Fail if k' = 0. - if IsZero(kk) { - panic("k is zero") - } - // Let R = k'⋅G. - R := EcBaseMul(kk) - // Let k = k' if has_even_y(R), otherwise let k = n - k' . - k := new(big.Int) - if HasEvenY(R) { - k.Set(kk) - } else { - k.Sub(G.N, kk) - } - // Let e = int(hashBIP0340/challenge(bytes(R) || bytes(P) || m)) mod n. - rb := R.ToBytes32() - eHash := BIP340HashChallenge(rb[:], pb[:], msg) - e := new(big.Int).Mod(FromBytes32(eHash), G.N) - // Let sig = bytes(R) || bytes((k + ed) mod n). - ed := new(big.Int).Mul(e, d) - ked := new(big.Int).Add(k, ed) - kedmod := new(big.Int).Mod(ked, G.N) - sig := BIP340Signature{ rb, ToBytes32(kedmod) } - - if !BIP340Verify(sig, pb, msg) { - panic("created signature does not verify") - } - - return sig -} - -func BIP340Verify(sig BIP340Signature, pkb [32]byte, msg []byte) bool { - P, err := LiftX(FromBytes32(pkb)) - if err != nil { - fmt.Println(err) - fmt.Println("liftX error") - return false - } - - // r is a coordinate of a point in the field - r := FromBytes32(sig.rb) - if r.Cmp(G.P) != -1 { - fmt.Println("r >= P") - return false - } - - // s is a scalar - s := FromBytes32(sig.sb) - if s.Cmp(G.N) != -1 { - fmt.Println("s >= N") - return false - } - - pb := P.ToBytes32() - // e := H2(concat(sig.rb[:], pb[:], msg[:])) - eHash := BIP340HashChallenge(sig.rb[:], pb[:], msg) - e := new(big.Int).Mod(FromBytes32(eHash), G.N) - - R := EcSub(EcBaseMul(s), EcMul(P, e)) - - if IsInf(R) { - fmt.Println("R infinite") - return false - } - - fmt.Println(R.Y) - // R.Y is not even - if R.Y.Bit(0) != 0 { - fmt.Println("R.Y not even") - return false - } - - if R.X.Cmp(r) != 0 { - fmt.Println(R.X) - fmt.Println(r) - fmt.Println("R.X != r") - return false - } - - return true -} - -/* -FIXME: unexpected R.X != r with following values of R.Y: - -case 1: - -23897722509207951717803159524980286455416184134365462808398302808970597218351 -106450779596764643922704909739894999128340084802897435504460256555956258459124 - -case 2: - -114983223195334487868794691485953220567913142093480264677244825913831095999902 -41933126816717622098184868261463488088630222827715045303692879282718368837800 - -case 3: - -R.X = 88237589896761293066782573873059305961250984105136818952129896190174887718311 -r = 92969380900914639994523238295275354849816377058067753582599362192585694290836 - -R.Y in aggregate: 53140585281022697158848720990634019024509591704429141788805026098482283599027 -R.Y in verify: 156620474446557016514927458068231588368087575834588512257661140247378008757 - -presumably fixed in computeChallenge() which previously used gc.X.Bytes() and pk.X.Bytes() -which could produce a differing result if either gc or pk had a length less than 32 bytes -*/ \ No newline at end of file diff --git a/bip340_test.go b/bip340_test.go deleted file mode 100644 index 7d33c55..0000000 --- a/bip340_test.go +++ /dev/null @@ -1,294 +0,0 @@ -package main - -import ( - "encoding/hex" - "fmt" - "math/big" - "testing" -) - -func FromHex(s string) *big.Int { - i, good := new(big.Int).SetString(s, 16) - if !good { - panic("FromHex parse fail") - } - return i -} - -func BytesFromHex(s string) []byte { - bs, err := hex.DecodeString(s) - if err != nil { - panic(err) - } - return bs -} - -func Bytes32FromHex(s string) [32]byte { - return ToBytes32(FromHex(s)) -} - -func SigsEqual(sigA, sigB BIP340Signature) bool { - rba := FromBytes32(sigA.rb) - rbb := FromBytes32(sigB.rb) - - sba := FromBytes32(sigA.sb) - sbb := FromBytes32(sigB.sb) - - return rba.Cmp(rbb) == 0 && sba.Cmp(sbb) == 0 -} - -func WithSK(t *testing.T, i int, sk, pk, aux, msg, sig string, res bool, comment string) { - sigbs := BytesFromHex(sig) - var rb [32]byte - var sb [32]byte - copy(rb[:], sigbs[0:32]) - copy(sb[:], sigbs[32:64]) - parsedSig := BIP340Signature{ rb, sb } - - skb := Bytes32FromHex(sk) - pkb := Bytes32FromHex(pk) - - auxb := BytesFromHex(aux) - msgb := BytesFromHex(msg) - - producedSig := BIP340Sign(skb, msgb, auxb) - - if !SigsEqual(producedSig, parsedSig) { - t.Fatalf("vector %d: produced signature differs from parsed signature", i) - } - - verificationResult := BIP340Verify(parsedSig, pkb, msgb) - if verificationResult != res { - t.Fatalf("vector %d: signature result %t does not match expected result %t (comment: %s)", i, verificationResult, res, comment) - } - - fmt.Printf("vector %d: result %t (comment: %s)\n", i, res, comment) -} - -func WithoutSK(t *testing.T, i int, pk, msg, sig string, res bool, comment string) { - sigbs := BytesFromHex(sig) - var rb [32]byte - var sb [32]byte - copy(rb[:], sigbs[0:32]) - copy(sb[:], sigbs[32:64]) - parsedSig := BIP340Signature{ rb, sb } - - pkb := Bytes32FromHex(pk) - - msgb := BytesFromHex(msg) - - verificationResult := BIP340Verify(parsedSig, pkb, msgb) - if verificationResult != res { - t.Fatalf("vector %d: signature result %t does not match expected result %t (comment: %s)", i, verificationResult, res, comment) - } - - fmt.Printf("vector %d: result %t (comment: %s)\n", i, res, comment) -} - -func TestVectors(t *testing.T) { - WithSK( - t, - 0, - "0000000000000000000000000000000000000000000000000000000000000003", - "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0", - true, - "", - ) - - WithSK( - t, - 1, - "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", - "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - "0000000000000000000000000000000000000000000000000000000000000001", - "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", - "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A", - true, - "", - ) - - WithSK( - t, - 2, - "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9", - "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", - "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906", - "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", - "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7", - true, - "", - ) - - WithSK( - t, - 3, - "0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710", - "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517", - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", - "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3", - true, - "test fails if msg is reduced modulo p or n", - ) - - WithoutSK( - t, - 4, - "D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9", - "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703", - "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4", - true, - "", - ) - - WithoutSK( - t, - 5, - "EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34", - "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", - "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B", - false, - "public key not on the curve", - ) - - WithoutSK( - t, - 6, - "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", - "FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2", - false, - "has_even_y(R) is false", - ) - - WithoutSK( - t, - 7, - "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", - "1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD", - false, - "negated message", - ) - - WithoutSK( - t, - 8, - "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", - "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6", - false, - "negated s value", - ) - - WithoutSK( - t, - 9, - "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", - "0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051", - false, - "sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0", - ) - - WithoutSK( - t, - 10, - "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", - "00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197", - false, - "sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1", - ) - - WithoutSK( - t, - 11, - "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", - "4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B", - false, - "sig[0:32] is not an X coordinate on the curve", - ) - - WithoutSK( - t, - 12, - "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B", - false, - "sig[0:32] is equal to field size", - ) - - WithoutSK( - t, - 13, - "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", - "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", - "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", - false, - "sig[32:64] is equal to curve order", - ) - - WithoutSK( - t, - 14, - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", - "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", - "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B", - false, - "public key is not a valid X coordinate because it exceeds the field size", - ) - - WithSK( - t, - 15, - "0340034003400340034003400340034003400340034003400340034003400340", - "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117", - "0000000000000000000000000000000000000000000000000000000000000000", - "", - "71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63", - true, - "message of size 0 (added 2022-12)", - ) - - WithSK( - t, - 16, - "0340034003400340034003400340034003400340034003400340034003400340", - "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117", - "0000000000000000000000000000000000000000000000000000000000000000", - "11", - "08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF", - true, - "message of size 1 (added 2022-12)", - ) - - WithSK( - t, - 17, - "0340034003400340034003400340034003400340034003400340034003400340", - "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117", - "0000000000000000000000000000000000000000000000000000000000000000", - "0102030405060708090A0B0C0D0E0F1011", - "5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5", - true, - "message of size 17 (added 2022-12)", - ) - - WithSK( - t, - 18, - "0340034003400340034003400340034003400340034003400340034003400340", - "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117", - "0000000000000000000000000000000000000000000000000000000000000000", - "99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", - "403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367", - true, - "message of size 100 (added 2022-12)", - ) -} \ No newline at end of file diff --git a/coordinator.go b/coordinator.go deleted file mode 100644 index 5e8a821..0000000 --- a/coordinator.go +++ /dev/null @@ -1,256 +0,0 @@ -package main - -import ( - "crypto/rand" - "fmt" - "math/big" -) - -const ( - // this coordinator will behave correctly at all times - GoodCoordinator = iota - // this coordinator will not announce its coordinator status and request commitments - DoesNotRequestCommits = iota - // this coordinator will not and request signature shares - DoesNotRequestSignature = iota - // this coordinator will not produce signatures from shares it has received - DoesNotAggregate = iota - // this coordinator will try to produce a signature for the wrong message - ChangesMessage = iota -) - -type RoastExecution struct { - group *GroupData - coordinatorIndex uint64 - behaviour int - message []byte - badMembers []uint64 - commits []Commit - requests map[[32]byte]RoastRequest -} - -type RoastRequest struct { - identifier [32]byte - Commitments []Commit - Responses map[uint64]*big.Int - Precalc *SigVerifyPrecalc -} - -func (R *RoastExecution) RequestCommits() *CommitRequest { - // - // Bad behaviour - // - if R.behaviour == DoesNotRequestCommits { - return nil - } - // - // - // - - return &CommitRequest{R.coordinatorIndex, R.message} -} - -func (R *RoastExecution) HasBad(i uint64) bool { - for _, bad := range R.badMembers { - if i == bad { - return true - } - } - return false -} - - -// Coordinator behaviour: -// Receive a response from a member -func (R *RoastExecution) ReceiveCommit(commit Commit) *SignRequest { - // filter out bad members' commits so we reject them in the future - if R.HasBad(commit.i) { - return nil - } - - R.commits = InsertCommit(R.commits, commit) - - if len(R.commits) != R.group.T { - return nil - } - - requestMessage := R.message - // - // Bad behaviour - // - if R.behaviour == DoesNotRequestSignature { - return nil - } - if R.behaviour == ChangesMessage { - requestMessage = make([]byte, 32) - _, err := rand.Read(requestMessage) - if err != nil { - panic(err) - } - } - // - // - // - - - cs := R.commits - csHash := CommitListHash(cs) - R.requests[csHash] = RoastRequest{ - csHash, - cs, - make(map[uint64]*big.Int), - &SigVerifyPrecalc{[]BindingFactor{}, nil}, - } - R.commits = make([]Commit, 0) - - return &SignRequest{R.coordinatorIndex, requestMessage, cs} -} - -func InsertCommit(ccs []Commit, c Commit) []Commit { - cs := ccs - commitCount := len(cs) - // This is the first commit; just add it to the end. - if commitCount == 0 { - cs = append(cs, c) - return cs - } - // This is not the first one, so iterate through the commitments. - // cc is a commit outside the list, - // whose correct insertion position we're trying to find. - // Since the list is sorted in ascending order, - // we skip commits until we find one whose identifier is higher than of cc, - // and insert cc there, shifting all commits afterwards by one position. - // This is good enough for the prototype, - // but a large group version can use a better data structure. - cc := c - for j := 0; j < commitCount; j++ { - ccc := cs[j] - // This is a duplicate commit; do not add it again. - if ccc.i == cc.i { - return cs - } - // If we find a commit whose identifier is higher - // than that of the commit we're inserting, - // swap it with the inserted commit. - // This keeps the commit list sorted in ascending order by ID. - if ccc.i > cc.i { - cs[j] = cc - cc = ccc - } - } - // We're done with the list, - // so append the remaining commit to the end. - cs = append(cs, cc) - - return cs -} - -func (R *RoastExecution) ReceiveShare(memberId uint64, requestId [32]byte, share *big.Int) *BIP340Signature { - req := R.requests[requestId] - cs := req.Commitments - res := req.Responses - found := false - var commit Commit - for _, c := range(cs) { - if c.i == memberId { - found = true - commit = c - } - } - if !found { - return nil - } - shareGood := verifySignatureSharePrecalc( - memberId, - R.group.PubkeyShares[memberId], - commit, - share, - cs, - R.group.Pubkey, - R.message, - req.Precalc, - ) - if !shareGood { - fmt.Printf("#") - R.badMembers = append(R.badMembers, memberId) - return nil - } - res[memberId] = share - - if len(res) != R.group.T { - return nil - } - // len(res) == T - shares := make([]*big.Int, 0) - - for _, share := range res { - shares = append(shares, share) - } - - sig := aggregate(R.group.Pubkey, cs, R.message, shares) - bipSig := ToBIP340(sig) - - // We return a good BIP-340 signature 50% of the time, - // so we need to check if this one is valid. - // If valid, we can finish. - sigGood := BIP340Verify(bipSig, R.group.Pubkey.ToBytes32(), R.message) - - if sigGood { - return &bipSig - } else { - return nil - } -} - -func SendSignRequests( - outChs map[uint64]MemberCh, - sr SignRequest, -) { - participants := participantsFromCommitList(sr.commits) - // fmt.Printf("requesting signatures from %v\n", participants) - for _, p := range participants { - outChs[p].sr <- sr - } -} - -func (R *RoastExecution) RunCoordinator( - inCh CoordinatorCh, - outChs map[uint64]MemberCh, -) BIP340Signature { - fmt.Printf("coordinator %v requesting commits\n", R.coordinatorIndex) - crptr := R.RequestCommits() - cr := *crptr - for i, out := range outChs { - if !R.HasBad(i) { - // fmt.Printf("sending request to member %v\n", out.i) - request := cr - out.cr <- request - } - } - - for { - select { - case commit := <- inCh.com: - // fmt.Printf("coordinator %v received commit from member %v\n", R.coordinatorIndex, commit.i) - sr := R.ReceiveCommit(commit) - if sr != nil { - SendSignRequests(outChs, *sr) - } - case share := <- inCh.shr: - // fmt.Printf("coordinator %v received share from member %v\n", R.coordinatorIndex, share.commit.i) - sig := R.ReceiveShare(share.commit.i, share.commitHash, share.share) - if sig != nil { - fmt.Println("successful signature") - for _, out := range outChs { - out.done <- true - } - return *sig - } else { - sr := R.ReceiveCommit(share.commit) - if sr != nil { - SendSignRequests(outChs, *sr) - } - } - } - } -} \ No newline at end of file diff --git a/coordinator_test.go b/coordinator_test.go deleted file mode 100644 index 3272de8..0000000 --- a/coordinator_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package main - -import ( - // "math/big" - "testing" -) - -func NullPoint() Point { - return Point{ nil, nil } -} - -func MockCommit(i int) Commit { - return Commit { uint64(i), NullPoint(), NullPoint() } -} - -func MockCommits(is []int) []Commit { - cs := make([]Commit, len(is)) - - for j, i := range is { - cs[j] = MockCommit(i) - } - - return cs -} - -func CommitsMatch(is []int, cs []Commit) bool { - if len(is) != len(cs) { - return false - } - for j, i := range is { - if cs[j].i != uint64(i) { - return false - } - } - return true -} - -func CheckMatch(t *testing.T, is []int, cs []Commit) { - if !CommitsMatch(is, cs) { - t.Fatalf("commits mismatch; expected %v, got %v", is, cs) - } -} - -func TestInsertCommit(t *testing.T) { - i123 := []int{1, 2, 3} - i124 := []int{1, 2, 4} - i1234 := []int{1, 2, 3, 4} - - CheckMatch( - t, - i1234, - InsertCommit(MockCommits(i123), MockCommit(4)), - ) - - CheckMatch( - t, - i1234, - InsertCommit(MockCommits(i124), MockCommit(3)), - ) - - CheckMatch( - t, - i123, - InsertCommit(MockCommits(i123), MockCommit(1)), - ) - - CheckMatch( - t, - []int{1}, - InsertCommit([]Commit{}, MockCommit(1)), - ) -} \ No newline at end of file diff --git a/curve.go b/curve.go deleted file mode 100644 index 047ab8a..0000000 --- a/curve.go +++ /dev/null @@ -1,121 +0,0 @@ -package main - -import ( - "crypto/rand" - // "crypto/sha256" - "github.com/ethereum/go-ethereum/crypto/secp256k1" - "math/big" -) - -type Curve secp256k1.BitCurve - -var curve = Curve(*secp256k1.S256()) -var G = &curve - -type Point struct { - X *big.Int // the X coordinate of the point - Y *big.Int // the Y coordinate of the point -} - -// Represent the X-coordinate of the point as a 32-byte array -func (P Point) ToBytes32() [32]byte { - var xb [32]byte - xbs := xb[:] - P.X.FillBytes(xbs) - return xb -} - -func (P Point) Bytes() []byte { - bb := make([]byte, 64) - P.X.FillBytes(bb[0:32]) - P.Y.FillBytes(bb[32:64]) - return bb -} - -func PointFrom(b []byte) Point { - xb := make([]byte, 32) - copy(xb, b[0:32]) - x := new(big.Int).SetBytes(xb) - yb := make([]byte, 32) - copy(yb, b[32:64]) - y := new(big.Int).SetBytes(yb) - return Point{x, y} -} - -func PointsEq(A, B Point) bool { - return A.X.Cmp(B.X) == 0 && A.Y.Cmp(B.Y) == 0 -} - -func Copy(P Point) Point { - x := new(big.Int).Set(P.X) - y := new(big.Int).Set(P.Y) - return Point{x, y} -} - -func (g *Curve) ID() Point { - return Point{big.NewInt(0), big.NewInt(0)} -} - -func IsInf(P Point) bool { - return P.X.Cmp(big.NewInt(0)) == 0 -} - -func HasEvenY(P Point) bool { - return P.Y.Bit(0) == 0 -} - -func EcMul(P Point, s *big.Int) Point { - sp := new(big.Int).Mod(s, G.N) - Ps_x, Ps_y := (*secp256k1.BitCurve)(G).ScalarMult(P.X, P.Y, sp.Bytes()) - return Point{Ps_x, Ps_y} -} - -func EcBaseMul(s *big.Int) Point { - sp := new(big.Int).Mod(s, G.N) - gs_x, gs_y := (*secp256k1.BitCurve)(G).ScalarBaseMult(sp.Bytes()) - return Point{gs_x, gs_y} -} - -func EcAdd(X Point, Y Point) Point { - XY_x, XY_y := (*secp256k1.BitCurve)(G).Add(X.X, X.Y, Y.X, Y.Y) - return Point{XY_x, XY_y} -} - -func EcSub(X Point, Y Point) Point { - Yneg := Point{Y.X, new(big.Int).Neg(Y.Y)} - return EcAdd(X, Yneg) -} - -func SampleFq() *big.Int { - b := make([]byte, G.BitSize/8) - i := new(big.Int) - for valid := false; !valid; { - _, err := rand.Read(b) - if err != nil { - panic(err) - } - i.SetBytes(b) - if i.Cmp(G.N) < 0 { - valid = true - } - } - return i -} - -func BytesToFq(b []byte) *big.Int { - x := new(big.Int) - x.SetBytes(b) - x.Mod(x, G.N) - - return x -} - -func (g *Curve) g() Point { - x := new(big.Int).Set(g.Gx) - y := new(big.Int).Set(g.Gy) - return Point{x, y} -} - -func (g *Curve) q() *big.Int { - return new(big.Int).Set(g.N) -} \ No newline at end of file diff --git a/frost.go b/frost.go deleted file mode 100644 index d4207dd..0000000 --- a/frost.go +++ /dev/null @@ -1,593 +0,0 @@ -package main - - -import ( - "crypto/rand" - "fmt" - "math/big" - // "github.com/ethereum/go-ethereum/crypto/secp256k1" -) - -// nonce generations that salts randomness with the secret -func genNonce(secret []byte) *big.Int { - b := make([]byte, 32) - _, err := rand.Read(b) - if err != nil { - panic(err) - } - - return H3(concat(b, secret)) -} - -type Commit struct { - i uint64 // participant index - hnc Point // hiding nonce commitment - bnc Point // blinding nonce commitment -} - -type BindingFactor struct { - i uint64 // participant index - bf *big.Int // binding factor -} - -type Nonce struct { - hn *big.Int // hiding nonce - bn *big.Int // blinding nonce -} - -type Signature struct { - R Point - z *big.Int -} - -type SigVerifyPrecalc struct { - bfs []BindingFactor - challenge *big.Int -} - -func toBytes(x uint64) []byte { - b := make([]byte, 8) - return big.NewInt(int64(x)).FillBytes(b) -} - -/* -4.3. List Operations - - This section describes helper functions that work on lists of values - produced during the FROST protocol. The following function encodes a - list of participant commitments into a byte string for use in the - FROST protocol. - - Inputs: - - commitment_list = [(i, hiding_nonce_commitment_i, - binding_nonce_commitment_i), ...], a list of commitments issued by - each participant, where each element in the list indicates a - NonZeroScalar identifier i and two commitment Element values - (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list - MUST be sorted in ascending order by identifier. - - Outputs: - - encoded_group_commitment, the serialized representation of - commitment_list, a byte string. -*/ -// def encode_group_commitment_list(commitment_list): -func encodeGroupCommitment(cs []Commit) []byte { - // encoded_group_commitment = nil - - // preallocate the necessary space to avoid waste - b := make([]byte, 0, 136 * len(cs)) - // for (identifier, hiding_nonce_commitment, - // binding_nonce_commitment) in commitment_list: - for _, ci := range cs { - // encoded_commitment = ( - // G.SerializeScalar(identifier) || - // G.SerializeElement(hiding_nonce_commitment) || - // G.SerializeElement(binding_nonce_commitment)) - // encoded_group_commitment = ( - // encoded_group_commitment || - // encoded_commitment) - - // length of commitment encoding: - // 8 bytes (uint64) - // 64 bytes (Point) - // 64 bytes (Point) - // = 136 bytes - b = append(b, toBytes(ci.i)...) - b = append(b, ci.hnc.Bytes()...) - b = append(b, ci.bnc.Bytes()...) - } - // return encoded_group_commitment - return b -} - -/* - The following function is used to extract identifiers from a - commitment list. - - Inputs: - - commitment_list = [(i, hiding_nonce_commitment_i, - binding_nonce_commitment_i), ...], a list of commitments issued by - each participant, where each element in the list indicates a - NonZeroScalar identifier i and two commitment Element values - (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list - MUST be sorted in ascending order by identifier. - - Outputs: - - identifiers, a list of NonZeroScalar values. -*/ -// def participants_from_commitment_list(commitment_list): -func participantsFromCommitList(cs []Commit) []uint64 { - // identifiers = [] - ms := make([]uint64, len(cs)) - prev := uint64(0) - - // for (identifier, _, _) in commitment_list: - // identifiers.append(identifier) - for i, ci := range cs { - ms[i] = ci.i - if (ci.i <= prev) { - panic("invalid order in commit list") - } - prev = ci.i - } - // return identifiers - return ms -} - -/* -The following function is used to extract a binding factor from a - list of binding factors. - - Inputs: - - binding_factor_list = [(i, binding_factor), ...], - a list of binding factors for each participant, where each element - in the list indicates a NonZeroScalar identifier i and Scalar - binding factor. - - identifier, participant identifier, a NonZeroScalar. - - Outputs: - - binding_factor, a Scalar. - - Errors: - - "invalid participant", when the designated participant is - not known. -*/ -// def binding_factor_for_participant(binding_factor_list, identifier): -func bindingFactorForParticipant(bfs []BindingFactor, i uint64) *big.Int { - // for (i, binding_factor) in binding_factor_list: - for _, b := range bfs { - // if identifier == i: - // return binding_factor - if (b.i == i) { - return b.bf - } - } - // raise "invalid participant" - panic("binding factor not found") -} - -/* -4.4. Binding Factors Computation - - This section describes the subroutine for computing binding factors - based on the participant commitment list, message to be signed, and - group public key. - -Inputs: -- group_public_key, the public key corresponding to the group signing - key, an Element. -- commitment_list = [(i, hiding_nonce_commitment_i, - binding_nonce_commitment_i), ...], a list of commitments issued by - each participant, where each element in the list indicates a - NonZeroScalar identifier i and two commitment Element values - (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list - MUST be sorted in ascending order by identifier. -- msg, the message to be signed. - -Outputs: -- binding_factor_list, a list of (NonZeroScalar, Scalar) tuples - representing the binding factors. -*/ -// def compute_binding_factors(group_public_key, commitment_list, msg): -func computeBindingFactors(pk Point, cs []Commit, msg []byte) []BindingFactor { - // group_public_key_enc = G.SerializeElement(group_public_key) - groupPubkeyEnc := pk.Bytes() - // // Hashed to a fixed-length. - // msg_hash = H4(msg) - msgHash := H4(msg) - // encoded_commitment_hash = - // H5(encode_group_commitment_list(commitment_list)) - encodedCommitHash := H5(encodeGroupCommitment(cs)) - // // The encoding of the group public key is a fixed length within a ciphersuite. - // rho_input_prefix = group_public_key_enc || msg_hash || encoded_commitment_hash - rhoInputPrefix := concat(groupPubkeyEnc, msgHash, encodedCommitHash) - - // binding_factor_list = [] - bfs := make([]BindingFactor, len(cs)) - - // for (identifier, hiding_nonce_commitment, - // binding_nonce_commitment) in commitment_list: - for j, cj := range cs { - // rho_input = rho_input_prefix || G.SerializeScalar(identifier) - rhoInput := concat(rhoInputPrefix, toBytes(cj.i)) - // binding_factor = H1(rho_input) - bf := H1(rhoInput) - // binding_factor_list.append((identifier, binding_factor)) - bfs[j] = BindingFactor{cj.i, bf} - } - // return binding_factor_list - return bfs -} - -/* -4.5. Group Commitment Computation - - This section describes the subroutine for creating the group - commitment from a commitment list. - - Inputs: - - commitment_list = [(i, hiding_nonce_commitment_i, - binding_nonce_commitment_i), ...], a list of commitments issued by - each participant, where each element in the list indicates a - NonZeroScalar identifier i and two commitment Element values - (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list - MUST be sorted in ascending order by identifier. - - binding_factor_list = [(i, binding_factor), ...], - a list of (NonZeroScalar, Scalar) tuples representing the binding - factor Scalar for the given identifier. - - Outputs: - - group_commitment, an Element. -*/ -// def compute_group_commitment(commitment_list, binding_factor_list): -func computeGroupCommitment(cs []Commit, bfs []BindingFactor) Point { - // group_commitment = G.Identity() - gc := G.ID() - - // for (identifier, hiding_nonce_commitment, - // binding_nonce_commitment) in commitment_list: - for _, ci := range cs { - // binding_factor = binding_factor_for_participant( - // binding_factor_list, identifier) - bf := bindingFactorForParticipant(bfs, ci.i) - // binding_nonce = G.ScalarMult( - // binding_nonce_commitment, - // binding_factor) - bn := EcMul(ci.bnc, bf) - // group_commitment = ( - // group_commitment + - // hiding_nonce_commitment + - // binding_nonce) - gc = EcAdd(gc, EcAdd(ci.hnc, bn)) - } - - // return group_commitment - return gc -} - -/* -4.6. Signature Challenge Computation - - This section describes the subroutine for creating the per-message - challenge. - - Inputs: - - group_commitment, the group commitment, an Element. - - group_public_key, the public key corresponding to the group signing - key, an Element. - - msg, the message to be signed, a byte string. - - Outputs: - - challenge, a Scalar. -*/ -// def compute_challenge(group_commitment, group_public_key, msg): -func computeChallenge(gc Point, pk Point, msg []byte) *big.Int { - // group_comm_enc = G.SerializeElement(group_commitment) - // group_public_key_enc = G.SerializeElement(group_public_key) - // challenge_input = group_comm_enc || group_public_key_enc || msg - // challenge = H2(challenge_input) - gcb := gc.ToBytes32() - pkb := pk.ToBytes32() - challenge := BIP340HashChallenge(gcb[:], pkb[:], msg) - // return challenge - return FromBytes32(challenge) -} - -/* -5.1. Round One - Commitment - - Round one involves each participant generating nonces and their - corresponding public commitments. A nonce is a pair of Scalar - values, and a commitment is a pair of Element values. Each - participant's behavior in this round is described by the commit - function below. Note that this function invokes nonce_generate - twice, once for each type of nonce produced. The output of this - function is a pair of secret nonces (hiding_nonce, binding_nonce) and - their corresponding public commitments (hiding_nonce_commitment, - binding_nonce_commitment). - - Inputs: - - sk_i, the secret key share, a Scalar. - - Outputs: - - (nonce, comm), a tuple of nonce and nonce commitment pairs, - where each value in the nonce pair is a Scalar and each value in - the nonce commitment pair is an Element. -*/ -// def commit(sk_i): -func round1(i uint64, ski *big.Int) (Nonce, Commit) { - // hiding_nonce = nonce_generate(sk_i) - hn := genNonce(ski.Bytes()) - // binding_nonce = nonce_generate(sk_i) - bn := genNonce(ski.Bytes()) - // hiding_nonce_commitment = G.ScalarBaseMult(hiding_nonce) - hnc := EcBaseMul(hn) - // binding_nonce_commitment = G.ScalarBaseMult(binding_nonce) - bnc := EcBaseMul(bn) - - // nonces = (hiding_nonce, binding_nonce) - // comms = (hiding_nonce_commitment, binding_nonce_commitment) - // return (nonces, comms) - return Nonce{hn, bn}, Commit{i, hnc, bnc} -} - -/* -5.2. Round Two - Signature Share Generation - - In round two, the Coordinator is responsible for sending the message - to be signed, and for choosing which participants will participate - (of number at least MIN_PARTICIPANTS). Signers additionally require - locally held data; specifically, their private key and the nonces - corresponding to their commitment issued in round one. - - The Coordinator begins by sending each participant the message to be - signed along with the set of signing commitments for all participants - in the participant list. Each participant MUST validate the inputs - before processing the Coordinator's request. In particular, the - Signer MUST validate commitment_list, deserializing each group - Element in the list using DeserializeElement from Section 3.1. 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. Applications which - require that participants not process arbitrary input messages are - also required to perform relevant application-layer input validation - checks; see Section 7.7 for more details. - - Upon receipt and successful input validation, each Signer then runs - the following procedure to produce its own signature share. - -Connolly, et al. Expires 22 March 2024 [Page 22] -Internet-Draft FROST September 2023 - -Inputs: -- identifier, identifier i of the participant, a NonZeroScalar. -- sk_i, Signer secret key share, a Scalar. -- group_public_key, public key corresponding to the group signing - key, an Element. -- nonce_i, pair of Scalar values (hiding_nonce, binding_nonce) - generated in round one. -- msg, the message to be signed, a byte string. -- commitment_list = [(i, hiding_nonce_commitment_i, - binding_nonce_commitment_i), ...], a list of commitments issued by - each participant, where each element in the list indicates a - NonZeroScalar identifier i and two commitment Element values - (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list - MUST be sorted in ascending order by identifier. - -Outputs: -- sig_share, a signature share, a Scalar. -*/ -// def sign(identifier, sk_i, group_public_key, -// nonce_i, msg, commitment_list): -func round2(i uint64, ski *big.Int, pk Point, nonce Nonce, msg []byte, cs []Commit) *big.Int { - // # Compute the binding factor(s) - // binding_factor_list = compute_binding_factors(group_public_key, commitment_list, msg) - bfs := computeBindingFactors(pk, cs, msg) - // binding_factor = binding_factor_for_participant(binding_factor_list, identifier) - bf := bindingFactorForParticipant(bfs, i) - - // # Compute the group commitment - // group_commitment = compute_group_commitment(commitment_list, binding_factor_list) - gc := computeGroupCommitment(cs, bfs) - - // # Compute the interpolating value - // participant_list = participants_from_commitment_list(commitment_list) - members := participantsFromCommitList(cs) - // lambda_i = derive_interpolating_value(participant_list, identifier) - lambda_i := deriveInterpolatingValue(i, members) - - // # Compute the per-message challenge - // challenge = compute_challenge(group_commitment, group_public_key, msg) - challenge := computeChallenge(gc, pk, msg) - - // # Compute the signature share - // (hiding_nonce, binding_nonce) = nonce_i - // sig_share = hiding_nonce + (binding_nonce * binding_factor) + (lambda_i * sk_i * challenge) - bnbf := new(big.Int).Mul(nonce.bn, bf) - lski := new(big.Int).Mul(lambda_i, ski) - lskic := new(big.Int).Mul(lski, challenge) - - bnbflskic := new(big.Int).Add(bnbf, lskic) - - sigShare := new(big.Int).Add(nonce.hn, bnbflskic) - - // return sig_share - return sigShare -} - -/* -5.3. Signature Share Aggregation - - After participants perform round two and send their signature shares - to the Coordinator, the Coordinator aggregates each share to produce - a final signature. Before aggregating, the Coordinator MUST validate - each signature share using DeserializeScalar. If validation fails, - the Coordinator MUST abort the protocol as the resulting signature - will be invalid. If all signature shares are valid, the Coordinator - aggregates them to produce the final signature using the following - procedure. - -Inputs: -- commitment_list = [(i, hiding_nonce_commitment_i, - binding_nonce_commitment_i), ...], a list of commitments issued by - each participant, where each element in the list indicates a - NonZeroScalar identifier i and two commitment Element values - (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list - MUST be sorted in ascending order by identifier. -- msg, the message to be signed, a byte string. -- group_public_key, public key corresponding to the group signing - key, an Element. -- sig_shares, a set of signature shares z_i, Scalar values, for each - participant, of length NUM_PARTICIPANTS, where - MIN_PARTICIPANTS <= NUM_PARTICIPANTS <= MAX_PARTICIPANTS. - -Outputs: -- (R, z), a Schnorr signature consisting of an Element R and - Scalar z. -*/ -// def aggregate(commitment_list, msg, group_public_key, sig_shares): -func aggregate(pk Point, cs []Commit, msg []byte, shares []*big.Int) Signature { - // # Compute the binding factors - // binding_factor_list = compute_binding_factors(group_public_key, commitment_list, msg) - bfs := computeBindingFactors(pk, cs, msg) - - // # Compute the group commitment - // group_commitment = compute_group_commitment(commitment_list, binding_factor_list) - gc := computeGroupCommitment(cs, bfs) - - // # Compute aggregated signature - // z = Scalar(0) - z := big.NewInt(0) - // for z_i in sig_shares: - // z = z + z_i - for _, zi := range shares { - z.Add(z, zi) - z.Mod(z, G.q()) - } - - e := computeChallenge(gc, pk, msg) - - R := EcSub(EcBaseMul(z), EcMul(pk, e)) - fmt.Println(R.Y) - - // return (group_commitment, z) - return Signature{gc, z} -} - -/* - The function for verifying a signature share, denoted - verify_signature_share, is described below. Recall that the - Coordinator is configured with "group info" which contains the group - public key PK and public keys PK_i for each participant, so the - group_public_key and PK_i function arguments MUST come from that - previously stored group info. - -Inputs: -- identifier, identifier i of the participant, a NonZeroScalar. -- PK_i, the public key for the i-th participant, where - PK_i = G.ScalarBaseMult(sk_i), an Element. -- comm_i, pair of Element values in G - (hiding_nonce_commitment, binding_nonce_commitment) generated in - round one from the i-th participant. -- sig_share_i, a Scalar value indicating the signature share as - produced in round two from the i-th participant. -- commitment_list = [(i, hiding_nonce_commitment_i, - binding_nonce_commitment_i), ...], a list of commitments issued by - each participant, where each element in the list indicates a - NonZeroScalar identifier i and two commitment Element values - (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list - MUST be sorted in ascending order by identifier. -- group_public_key, public key corresponding to the group signing - key, an Element. -- msg, the message to be signed, a byte string. - -Outputs: -- True if the signature share is valid, and False otherwise. -*/ -// def verify_signature_share( -// identifier, PK_i, comm_i, sig_share_i, commitment_list, -// group_public_key, msg): -func verifySignatureShare( - i uint64, - pk_i Point, - commit_i Commit, - sigShare_i *big.Int, - cs []Commit, - pk Point, - msg []byte, -) bool { - // # Compute the binding factors - // binding_factor_list = compute_binding_factors(group_public_key, commitment_list, msg) - bfs := computeBindingFactors(pk, cs, msg) - // binding_factor = binding_factor_for_participant(binding_factor_list, identifier) - bf := bindingFactorForParticipant(bfs, i) - - // # Compute the group commitment - // group_commitment = compute_group_commitment(commitment_list, binding_factor_list) - gc := computeGroupCommitment(cs, bfs) - - // # Compute the commitment share - // (hiding_nonce_commitment, binding_nonce_commitment) = comm_i - // comm_share = hiding_nonce_commitment + G.ScalarMult( - // binding_nonce_commitment, binding_factor) - cShare := EcAdd(commit_i.hnc, EcMul(commit_i.bnc, bf)) - - // # Compute the challenge - // challenge = compute_challenge( - // group_commitment, group_public_key, msg) - challenge := computeChallenge(gc, pk, msg) - - // # Compute the interpolating value - // participant_list = participants_from_commitment_list(commitment_list) - members := participantsFromCommitList(cs) - // lambda_i = derive_interpolating_value(participant_list, identifier) - lambda_i := deriveInterpolatingValue(i, members) - - cli := new(big.Int).Mul(challenge, lambda_i) - - // # Compute relation values - // l = G.ScalarBaseMult(sig_share_i) - l := EcBaseMul(sigShare_i) - // r = comm_share + G.ScalarMult(PK_i, challenge * lambda_i) - r := EcAdd(cShare, EcMul(pk_i, cli)) - - // return l == r - return PointsEq(l, r) -} - -// Same as above, but use cached binding factors and challenge, -// only calculating them on the first call on the attempt. -// This constitutes a major saving in coordinator overhead, -// especially with large group sizes. -func verifySignatureSharePrecalc( - i uint64, - pk_i Point, - commit_i Commit, - sigShare_i *big.Int, - cs []Commit, - pk Point, - msg []byte, - precalc *SigVerifyPrecalc, -) bool { - // Use the challenge's *big.Int pointer to see whether values have been cached. - if precalc.challenge == nil { - bfs := computeBindingFactors(pk, cs, msg) - gc := computeGroupCommitment(cs, bfs) - challenge := computeChallenge(gc, pk, msg) - - precalc.bfs = bfs - precalc.challenge = challenge - } - bf := bindingFactorForParticipant(precalc.bfs, i) - cShare := EcAdd(commit_i.hnc, EcMul(commit_i.bnc, bf)) - members := participantsFromCommitList(cs) - lambda_i := deriveInterpolatingValue(i, members) - cli := new(big.Int).Mul(precalc.challenge, lambda_i) - l := EcBaseMul(sigShare_i) - r := EcAdd(cShare, EcMul(pk_i, cli)) - return PointsEq(l, r) -} - -func ToBIP340(sig Signature) BIP340Signature { - return BIP340Signature{ ToBytes32(sig.R.X), ToBytes32(sig.z) } -} \ No newline at end of file diff --git a/go.mod b/go.mod index ff135ee..ac71856 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module threshold.network/roast go 1.21.0 - -require github.com/ethereum/go-ethereum v1.13.1 // indirect diff --git a/go.sum b/go.sum index 0b3d39c..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +0,0 @@ -github.com/ethereum/go-ethereum v1.13.1 h1:UF2FaUKPIy5jeZk3X06ait3y2Q4wI+vJ1l7+UARp+60= -github.com/ethereum/go-ethereum v1.13.1/go.mod h1:xHQKzwkHSl0gnSjZK1mWa06XEdm9685AHqhRknOzqGQ= diff --git a/hash.go b/hash.go deleted file mode 100644 index adede26..0000000 --- a/hash.go +++ /dev/null @@ -1,106 +0,0 @@ -package main - -import ( - "crypto/sha256" - "crypto/subtle" - "math/big" -) - -var contextString = []byte("FROST-secp256k1-SHA256-v11") - -var bip340ChallengeTag = []byte("BIP0340/challenge") -var bip340AuxTag = []byte("BIP0340/aux") -var bip340NonceTag = []byte("BIP0340/nonce") - -func BIP340Hash(tag, m []byte) [32]byte { - hashedTag := sha256.Sum256(tag) - slicedTag := hashedTag[:] - hashed := sha256.Sum256(concat(slicedTag, slicedTag, m)) - - return hashed -} - -func BIP340HashChallenge(ms ...[]byte) [32]byte { - return BIP340Hash(bip340ChallengeTag, concat(ms[0], ms[1:]...)) -} - -func BIP340HashAux(ms ...[]byte) [32]byte { - return BIP340Hash(bip340AuxTag, concat(ms[0], ms[1:]...)) -} - -func BIP340HashNonce(ms ...[]byte) [32]byte { - return BIP340Hash(bip340NonceTag, concat(ms[0], ms[1:]...)) -} - -func H(m []byte) []byte { - hashed := sha256.Sum256(m) - return hashed[:] -} - -func H1(m []byte) *big.Int { - DST := concat(contextString, []byte("rho")) - return hashToScalar(DST, m) -} - -func H2(m []byte) *big.Int { - return hashToScalar(bip340ChallengeTag, m) -} - -func H3(m []byte) *big.Int { - DST := concat(contextString, []byte("nonce")) - return hashToScalar(DST, m) -} - -func H4(m []byte) []byte { - hashed := BIP340Hash(concat(contextString, []byte("msg")), m) - return hashed[:] -} - -func H5(m []byte) []byte { - hashed := BIP340Hash(concat(contextString, []byte("com")), m) - return hashed[:] -} - -func hashToScalar(tag, msg []byte) *big.Int { - hashed := BIP340Hash(tag, msg) - - ej := OS2IP(hashed[:]) - ej.Mod(ej, G.N) - - return ej -} - -func OS2IP(b []byte) *big.Int { - return new(big.Int).SetBytes(b) -} - -func I2OSP(x *big.Int, xLen uint64) []byte { - buf := make([]byte, xLen) - return x.FillBytes(buf) -} - -func HashToInt(msg []byte) *big.Int { - h := sha256.Sum256(msg) - return FromBytes32(h) -} - -// Safe concatenation of byte slices: -// Using append(a, b...) can modify a by extending its length -// if it has sufficient capacity to hold b. -// To avoid this in all circumstances, copy a to a new array -// before appending the rest. -func concat(a []byte, bs ...[]byte) []byte { - c := make([]byte, len(a)) - copy(c, a) - for _, b := range bs { - c = append(c, b...) - } - return c -} - -func xor(a, b []byte) []byte { - l := min(len(a), len(b)) - dest := make([]byte, l) - subtle.XORBytes(dest, a, b) - return dest -} \ No newline at end of file diff --git a/int.go b/int.go deleted file mode 100644 index 8ab0be1..0000000 --- a/int.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "math/big" -) - -func ToBytes32(i *big.Int) [32]byte { - var b [32]byte - bs := b[:] - i.FillBytes(bs) - return b -} - -func FromBytes32(b [32]byte) *big.Int { - return new(big.Int).SetBytes(b[:]) -} - -func IsZero(x *big.Int) bool { - return x.Cmp(big.NewInt(0)) == 0 -} - -func BigintEq(x, y *big.Int) bool { - return x.Cmp(y) == 0 -} \ No newline at end of file diff --git a/member.go b/member.go deleted file mode 100644 index 04505da..0000000 --- a/member.go +++ /dev/null @@ -1,169 +0,0 @@ -package main - -import ( - "crypto/rand" - // "fmt" - "math/big" -) - - -const ( - // this member will behave correctly at all times - GoodMember = iota - // this member will not send commitments to the coordinator - DoesNotCommit = iota - // this member will send commitments but will not send signature shares - DoesNotRespond = iota - // this member will send invalid signature shares - RespondsMaliciously = iota - // This member will send invalid signature shares, - // but only if it is the lowest-indexed member in the group. - // Use this to simulate coordinated malicious behaviour. - WithMaliceAforethought = iota - // Like above, but it refrains from responding - // instead of responding with invalid shares. - MaliciouslyInactive = iota -) - -type MemberResponse struct { - commit Commit - nonce Nonce - spent bool -} - -type MemberState struct { - i uint64 - behaviour int - pk Point - pki Point - ski *big.Int - // hash(coordinator index, commitHash) -> response to that coordinator - responses map[[32]byte]*MemberResponse -} - -// respond to a commit request -func (S *MemberState) RespondC(r CommitRequest) *Commit { - // - // Bad behaviour - // - b := S.behaviour - if b == DoesNotCommit { - return nil - } - // - // - // - - n, c := round1(S.i, S.ski) - - res := MemberResponse{ c, n, false } - - rh := ResponseHash(c, r.coordinator) - - S.responses[rh] = &res - - return &c -} - -// respond to a signing request -func (S *MemberState) RespondS(r SignRequest) *SignatureShare { - // - // Bad behaviour - // - b := S.behaviour - // return nothing - if b == DoesNotRespond { - return nil - } - // - // - // - - var found *MemberResponse - found = nil - - requestId := CommitListHash(r.commits) - - firstCommit := r.commits[0] - if firstCommit.i == S.i { - if b == WithMaliceAforethought { - b = RespondsMaliciously - } else if b == MaliciouslyInactive { - return nil - } - } - - for _, c := range r.commits { - if c.i == S.i { - rh := ResponseHash(c, r.coordinator) - found = S.responses[rh] - } - } - - // we have not made a commit for this round - // or we have already used the nonce for signing - if found == nil || found.spent { - return nil - } - - // Make a new commit - nn, cc := round1(S.i, S.ski) - - newres := MemberResponse{ cc, nn, false } - - rhh := ResponseHash(cc, r.coordinator) - - S.responses[rhh] = &newres - - // - // Bad behaviour - // - // return an invalid value - if b == RespondsMaliciously { - bs := make([]byte, 32) - _, err := rand.Read(bs) - if err != nil { - panic(err) - } - return &SignatureShare{requestId, HashToInt(bs), cc} - } - // - // - // - - n := found.nonce - - share := round2(S.i, S.ski, S.pk, n, r.message, r.commits) - - // Wipe the nonce to prevent reuse - found.nonce = Nonce{nil, nil} - found.spent = true - - return &SignatureShare{requestId, share, cc} -} - -func (S *MemberState) RunMember( - outCh CoordinatorCh, - inCh MemberCh, -) { - for { - select { - case cr := <- inCh.cr: - // fmt.Printf("member %v responding to commit request\n", S.i) - commit := S.RespondC(cr) - if commit != nil { - outCh.com <- *commit - } - case sr := <- inCh.sr : - // fmt.Printf("member %v responding to sign request\n", S.i) - share := S.RespondS(sr) - if share != nil { - s := *share - outCh.shr <- s - } - case <-inCh.done: - // fmt.Printf("member %v done\n", S.i) - return - } - } -} \ No newline at end of file diff --git a/poly.go b/poly.go deleted file mode 100644 index b30b054..0000000 --- a/poly.go +++ /dev/null @@ -1,105 +0,0 @@ -package main - -import ( - "crypto/rand" - "math/big" -) - -// Create a polynomial with degree t-1 -// Return an array of t elements, -// where each term of the polynomial equals arr[i] * x^i -func GenPoly(secret *big.Int, t int) []*big.Int { - arr := make([]*big.Int, t) - arr[0] = secret - for i := 1; i < t ; i++ { - b := make([]byte, 32) - _, err := rand.Read(b) - if err != nil { - panic(err) - } - arr[i] = new(big.Int).Mod(OS2IP(b), G.q()) - } - return arr -} - -func CalculatePoly(coeffs []*big.Int, x int) *big.Int { - res := new(big.Int) - - bigX := big.NewInt(int64(x)) - - for i, coeff := range coeffs { - tmp := new(big.Int).Exp(bigX, big.NewInt(int64(i)), G.q()) - tmp.Mul(tmp, coeff) - res.Add(res, tmp) - } - return res -} - -/* -4.2. Polynomials - - This section defines polynomials over Scalars that are used in the - main protocol. A polynomial of maximum degree t is represented as a - list of t+1 coefficients, where the constant term of the polynomial - is in the first position and the highest-degree coefficient is in the - last position. For example, the polynomial x^2 + 2x + 3 has degree 2 - and is represented as a list of 3 coefficients [3, 2, 1]. A point on - the polynomial f is a tuple (x, y), where y = f(x). - - The function derive_interpolating_value derives a value used for - polynomial interpolation. It is provided a list of x-coordinates as - input, each of which cannot equal 0. - - Inputs: - - L, the list of x-coordinates, each a NonZeroScalar. - - x_i, an x-coordinate contained in L, a NonZeroScalar. - - Outputs: - - value, a Scalar. - - Errors: - - "invalid parameters", if 1) x_i is not in L, or if 2) any - x-coordinate is represented more than once in L. -*/ -// def derive_interpolating_value(L, x_i): -func deriveInterpolatingValue(xi uint64, L []uint64) *big.Int { - found := false - // numerator = Scalar(1) - num := big.NewInt(1) - // denominator = Scalar(1) - den := big.NewInt(1) - // for x_j in L: - for _, xj := range L { - if (xj == xi) { - // for x_j in L: - // if count(x_j, L) > 1: - // raise "invalid parameters" - if found { - panic("invalid parameters") - } - found = true - // if x_j == x_i: continue - continue - } - // numerator *= x_j - num.Mul(num, big.NewInt(int64(xj))) - num.Mod(num, G.N) - // denominator *= x_j - x_i - den.Mul(den, big.NewInt(int64(xj) - int64(xi))) - den.Mod(den, G.N) - } - - // if x_i not in L: - // raise "invalid parameters" - if !found { - panic("invalid parameters") - } - // value = numerator / denominator - // return value - - denInv := new(big.Int).ModInverse(den, G.N) - res := new(big.Int).Mul(num, denInv) - res = res.Mod(res, G.N) - - return res -} \ No newline at end of file diff --git a/poly_test.go b/poly_test.go deleted file mode 100644 index 53d33b7..0000000 --- a/poly_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "math/big" - "testing" -) - -func TestCalculatePoly(t *testing.T) { - // 3x^2 + 2x + 1 - coeffs := []*big.Int{ - big.NewInt(1), - big.NewInt(2), - big.NewInt(3), - } - - // x = 0 - // f(x) = 1 - res1 := CalculatePoly(coeffs, 0) - if res1.Cmp(big.NewInt(1)) != 0 { - t.Fatalf("x = 0 returns %d instead of 1", res1.Int64()) - } - - // x = 1 - // f(x) = 3 + 2 + 1 = 6 - res2 := CalculatePoly(coeffs, 1) - if res2.Cmp(big.NewInt(6)) != 0 { - t.Fatalf("x = 1 returns %d instead of 6", res1.Int64()) - } - - // x = 2 - // f(x) = 12 + 4 + 1 = 17 - res3 := CalculatePoly(coeffs, 2) - if res3.Cmp(big.NewInt(17)) != 0 { - t.Fatalf("x = 2 returns %d instead of 17", res1.Int64()) - } -} \ No newline at end of file diff --git a/protocol.go b/protocol.go deleted file mode 100644 index 4604248..0000000 --- a/protocol.go +++ /dev/null @@ -1,215 +0,0 @@ -package main - -import ( - "crypto/rand" - "fmt" - "math/big" - "os" - "runtime/pprof" - "sync" - "time" -) - -type Member struct { - i uint64 - skShare *big.Int - pkShare Point -} - -func main() { - f, err := os.Create("roast.prof") - if err != nil { - panic(err) - } - pprof.StartCPUProfile(f) - defer pprof.StopCPUProfile() - n := 400 - t := 201 - - // members, pk := RunKeygen(n, t) - - // msg := []byte("message") - - /* - - // round 1 - participants := members[0:t] - nonces := make([]Nonce, t) - commits := make([]Commit, t) - - for j, memberJ := range participants { - nonceJ, commitJ := round1(memberJ.i, memberJ.skShare) - - nonces[j] = nonceJ - commits[j] = commitJ - } - - // round 2 - shares := make([]*big.Int, t) - - for j, memberJ := range participants { - shares[j] = round2(memberJ.i, memberJ.skShare, pk, nonces[j], msg, commits) - - if !verifySignatureShare(memberJ.i, memberJ.pkShare, commits[j], shares[j], commits, pk, msg) { - fmt.Println("signature share failure") - } - } - - sig := aggregate(pk, commits, msg, shares) - - fmt.Println("--- verify signature ---") - - valid := BIP340Verify(ToBIP340(sig), ToBytes32(pk.X), msg) - - fmt.Println(valid) -*/ - - coordinatedInvalidShares := make([]int, n - t) - for i := range coordinatedInvalidShares { - coordinatedInvalidShares[i] = WithMaliceAforethought - } - coordinatedInactivity := make([]int, n - t) - for i := range coordinatedInactivity { - coordinatedInactivity[i] = MaliciouslyInactive - } - - start := time.Now() - RunRoastCh(n, t, coordinatedInvalidShares) - end := time.Now() - duration := end.Sub(start) - fmt.Printf("coordinated invalid shares: %v\n\n", duration) - - /* - start = time.Now() - RunRoastCh(n, t, coordinatedInactivity) - end = time.Now() - duration = end.Sub(start) - fmt.Printf("coordinated inactivity: %v\n\n", duration) - */ -} - -func RunKeygen(n, t int) ([]Member, Point) { - sk, pk := GenSharedKey() - - coeffs := GenPoly(sk, t) - - members := make([]Member, n) - - for j := range members { - i := j + 1 - skShare := CalculatePoly(coeffs, i) - pkShare := EcBaseMul(skShare) - - members[j] = Member{ uint64(i), skShare, pkShare } - } - - return members, pk -} - -func GenSharedKey() (*big.Int, Point) { - b := make([]byte, 32) - _, err := rand.Read(b) - if err != nil { - panic(err) - } - - sk := OS2IP(b) - sk.Mod(sk, G.q()) - - pk := EcBaseMul(sk) - - if !HasEvenY(pk) { - sk.Sub(G.q(), sk) - pk = EcBaseMul(sk) - } - - return sk, pk -} - -func RunRoastCh(n, t int, corruption []int) { - group, members := Initialise(n, t) - CorruptMembers(members, corruption) - - memberChs := make(map[uint64]MemberCh) - for _, member := range members { - // fmt.Printf("preparing member channel %v\n", member.i) - memberChs[member.i] = MemberCh { - member.i, - make(chan CommitRequest, 10), - make(chan SignRequest, 10), - make(chan bool, 1), - } - } - coordinatorCh := CoordinatorCh { - make(chan Commit, n*2), - make(chan SignatureShare, n*2), - } - - r1 := group.NewCoordinator([]byte("test"), 1) - - var wg sync.WaitGroup - wg.Add(len(members) + 1) - - for _, member := range members { - go func(member MemberState, ch MemberCh) { - defer wg.Done() - member.RunMember(coordinatorCh, ch) - }(member, memberChs[member.i]) - } - - go func() { - defer wg.Done() - r1.RunCoordinator(coordinatorCh, memberChs) - }() - - wg.Wait() -} - -func RunRoast(n, t int) { - group, members := Initialise(n, t) - - members[1].behaviour = DoesNotCommit - - r1 := group.NewCoordinator([]byte("test"), 1) - - commitRequest := r1.RequestCommits() - - commits := make([]Commit, 0) - - for _, member := range members { - fmt.Printf("requesting commit from member %v\n", member.i) - commit := member.RespondC(*commitRequest) - if commit != nil { - commits = append(commits, *commit) - } - } - - var signRequest *SignRequest - for _, commit := range commits { - fmt.Printf("processing commit from member %v\n", commit.i) - sr := r1.ReceiveCommit(commit) - if sr != nil { - signRequest = sr - break - } - } - - shares := make([]SignatureShare, 0) - for _, commit := range signRequest.commits { - member := members[commit.i - 1] - fmt.Printf("requesting signature from member %v\n", commit.i) - share := member.RespondS(*signRequest) - if share != nil { - shares = append(shares, *share) - commits = append(commits, share.commit) - } - } - - for _, share := range shares { - fmt.Printf("processing share %v\n", share.commit.i) - sig := r1.ReceiveShare(share.commit.i, share.commitHash, share.share) - if sig != nil { - fmt.Printf("successful signature\n") - } - } -} \ No newline at end of file diff --git a/roast.go b/roast.go deleted file mode 100644 index 18387d5..0000000 --- a/roast.go +++ /dev/null @@ -1,143 +0,0 @@ -package main - -import ( - "math/big" - // "math/rand" - // "time" -) - - -type GroupData struct { - Pubkey Point - N int - T int - Ids []uint64 - PubkeyShares map[uint64]Point -} - -type CommitRequest struct { - coordinator uint64 - message []byte -} - -type SignRequest struct { - coordinator uint64 - message []byte - commits []Commit -} - -type SignatureShare struct { - commitHash [32]byte - share *big.Int - commit Commit -} - -// Representing the incoming communications of the coordinator -type CoordinatorCh struct { - com chan Commit - shr chan SignatureShare -} - -// Representing a member's incoming communications -type MemberCh struct { - i uint64 - cr chan CommitRequest - sr chan SignRequest - done chan bool -} - -func Initialise(n, t int) (*GroupData, []MemberState) { - memberIds := make([]uint64, n) - memberStates := make([]MemberState, n) - - sk, pk := GenSharedKey() - - group := GroupData{ - pk, - n, - t, - memberIds, - make(map[uint64]Point), - } - - coeffs := GenPoly(sk, t) - - for j := range memberStates { - i := j + 1 - group.Ids[j] = uint64(i) - skShare := CalculatePoly(coeffs, i) - pkShare := EcBaseMul(skShare) - - memberStates[j] = MemberState{ - uint64(i), - GoodMember, - pk, - pkShare, - skShare, - make(map[[32]byte]*MemberResponse), - } - - group.PubkeyShares[uint64(i)] = pkShare - } - - return &group, memberStates -} - -func (G *GroupData) NewCoordinator(message []byte, i uint64) *RoastExecution { - return &RoastExecution{ - G, - i, - GoodCoordinator, - message, - make([]uint64, 0), - make([]Commit, 0), - make(map[[32]byte]RoastRequest), - } -} - -func CorruptMembers(members []MemberState, behaviours []int) { - for i, b := range behaviours { - members[i].behaviour = b - } -} - -func (R *RoastExecution) CorruptCoordinator(behaviour int) { - R.behaviour = behaviour -} - -func RoundId(coordinatorIndex uint64, roundNumber uint64) [32]byte { - a := big.NewInt(int64(coordinatorIndex)) - b := big.NewInt(int64(roundNumber)) - abs := ToBytes32(a) - bbs := ToBytes32(b) - - tag := []byte("roast/round_id") - msg := concat(abs[:], bbs[:]) - - return BIP340Hash(tag, msg) -} - -func CommitHash(c Commit) [32]byte { - tag := []byte("roast/commit_hash") - ib := I2OSP(big.NewInt(int64(c.i)), 8) // FIXME: jank but will do for now - return BIP340Hash(tag, concat(ib, c.hnc.Bytes(), c.bnc.Bytes())) -} - -func CommitListHash(cs []Commit) [32]byte { - bs := make([]byte, 0, 32 * len(cs)) - - for _, c := range cs { - h := CommitHash(c) - bs = append(bs, h[:]...) - } - - tag := []byte("roast/commit_list_hash") - return BIP340Hash(tag, bs) -} - -func ResponseHash(c Commit, coordinator uint64) [32]byte { - tag := []byte("roast/response_hash") - ib := I2OSP(big.NewInt(int64(c.i)), 8) // FIXME: jank but will do for now - cb := I2OSP(big.NewInt(int64(coordinator)), 8) // jank - return BIP340Hash(tag, concat(ib, c.hnc.Bytes(), c.bnc.Bytes(), cb)) -} \ No newline at end of file