From 5f6d639793bf7243988dbe24cfe24e64b3754a71 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Fri, 13 Oct 2023 06:46:19 -0700 Subject: [PATCH 1/6] Add initial BBS implementation sketch --- bbs/bbs.go | 684 ++++++++++++++++++++++++++++++++++++++++++++++++ bbs/bbs_test.go | 66 +++++ bbs/doc.go | 2 + 3 files changed, 752 insertions(+) create mode 100644 bbs/bbs.go create mode 100644 bbs/bbs_test.go create mode 100644 bbs/doc.go diff --git a/bbs/bbs.go b/bbs/bbs.go new file mode 100644 index 000000000..ab733e5b4 --- /dev/null +++ b/bbs/bbs.go @@ -0,0 +1,684 @@ +package bbs + +import ( + "crypto" + "crypto/rand" + "encoding/binary" + "encoding/hex" + "fmt" + "math/big" + + "github.com/cloudflare/circl/ecc/bls12381" + pairing "github.com/cloudflare/circl/ecc/bls12381" + "github.com/cloudflare/circl/expander" +) + +var ( + ciphersuiteID = []byte{0x00, 0x01} +) + +func ciphersuiteString(suffix string) []byte { + return append(ciphersuiteID, []byte(suffix)...) +} + +type Signature struct { + A *pairing.G1 + e *pairing.Scalar +} + +// (Abar, Bbar, r2^, r3^, (m^_j1, ..., m^_jU), c) +type Proof struct { + Abar *pairing.G1 + Bbar *pairing.G1 + r2h *pairing.Scalar + r3h *pairing.Scalar + commitments []*pairing.Scalar + c *pairing.Scalar +} + +// Key generation +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-key-generation-operations +func keyGen(ikm []byte, keyInfo, keyDst []byte) (*pairing.Scalar, error) { + if len(ikm) < 32 { + return nil, fmt.Errorf("bbs: invalid keyGen ikm") + } + if len(keyInfo) > 65535 { + return nil, fmt.Errorf("bbs: invalid keyGen keyInfo") + } + if keyDst == nil { + // keyDst = ciphersuite_id || "KEYGEN_DST_" + keyDst = ciphersuiteString("KEYGEN_DST_") + } + + // derive_input = key_material || I2OSP(length(key_info), 2) || key_info + lenBuffer := make([]byte, 2) + binary.BigEndian.PutUint16(lenBuffer, uint16(len(keyInfo))) + deriveInput := append(append(ikm, lenBuffer...), keyInfo...) + + // SK = hash_to_scalar(derive_input, key_dst) + sk := hashToScalar(deriveInput, keyDst) + + // if SK is INVALID, return INVALID + // XXX(caw): what does it mean for SK to be invalid if hash_to_scalar never returns an invalid scalar? + + return sk, nil +} + +func publicKey(sk *pairing.Scalar) []byte { + W := pairing.G2Generator() + W.ScalarMult(sk, W) + + // XXX(caw): point_to_octets_g2 + return W.Bytes() +} + +// Serialize +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-serialize +// XXX(caw): this function feels too complicated and should instead be replaced separate serializeFunctions invoked directly + +// Domain calculation +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-domain-calculation +func calculateDomain(pk []byte, Q1 *pairing.G1, hPoints []*pairing.G1, header []byte) *pairing.Scalar { + // XXX(caw): check for length of header + + // L = length(H_Points) + L := len(hPoints) + lenBuffer := make([]byte, 8) + binary.BigEndian.PutUint64(lenBuffer, uint64(L)) + + // 1. dom_array = (L, Q_1, H_1, ..., H_L) + // 2. dom_octs = serialize(dom_array) || ciphersuite_id + octets := make([]byte, 0) + octets = append(octets, lenBuffer...) + octets = append(octets, Q1.Bytes()...) + for _, hi := range hPoints { + octets = append(octets, hi.Bytes()...) + } + octets = append(octets, ciphersuiteID...) + + // 3. dom_input = PK || dom_octs || I2OSP(length(header), 8) || header + domInput := append(pk, octets...) + binary.BigEndian.PutUint64(lenBuffer, uint64(len(header))) + domInput = append(domInput, lenBuffer...) + domInput = append(domInput, header...) + + // 4. return hash_to_scalar(dom_input) + // XXX(caw): this should have an explicit DST + return hashToScalar(domInput, nil) +} + +func encodeInt(x int) []byte { + xBuffer := make([]byte, 8) + binary.BigEndian.PutUint64(xBuffer, uint64(x)) + return xBuffer +} + +func concat(x, y []byte) []byte { + fmt.Println(hex.EncodeToString(y)) + return append(x, y...) +} + +// Challenge calculation +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-challenge-calculation +func calculateChallenge(Abar, Bbar, C *pairing.G1, indexArray []int, msgArray []*pairing.Scalar, domain *pairing.Scalar, ph []byte) (*pairing.Scalar, error) { + // Deserialization: + + // 1. R = length(i_array) + // 2. (i1, ..., iR) = i_array + // 3. (msg_i1, ..., msg_iR) = msg_array + + // ABORT if: + + // 1. R > 2^64 - 1 or R != length(msg_array) + // 2. length(ph) > 2^64 - 1 + + // Procedure: + + // 1. c_arr = (Abar, Bbar, C, R, i1, ..., iR, msg_i1, ..., msg_iR, domain) + // 2. c_octs = serialize(c_array) + challengeInput := []byte{} + challengeInput = concat(challengeInput, Abar.Bytes()) + challengeInput = concat(challengeInput, Bbar.Bytes()) + challengeInput = concat(challengeInput, C.Bytes()) + for i := 0; i < len(indexArray); i++ { + challengeInput = concat(challengeInput, encodeInt(indexArray[i])) + } + for i := 0; i < len(msgArray); i++ { + msgEnc, err := msgArray[i].MarshalBinary() + if err != nil { + return nil, err + } + challengeInput = concat(challengeInput, msgEnc) + } + domainEnc, err := domain.MarshalBinary() + if err != nil { + return nil, err + } + challengeInput = concat(challengeInput, domainEnc) + + // 3. return hash_to_scalar(c_octs || I2OSP(length(ph), 8) || ph) + challengeInput = concat(challengeInput, encodeInt(len(ph))) + challengeInput = concat(challengeInput, ph) + + dst := []byte("TODO") + return hashToScalar(challengeInput, dst), nil +} + +// Generators calculation +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-generators-calculation +func createGenerators(count int, pk []byte) []*pairing.G1 { + // create_generators(count, PK) := hash_to_generator(count) + return hashToGenerators(count) +} + +func hashToGenerators(count int) []*pairing.G1 { + // ABORT if: + + // 1. count > 2^64 - 1 + + // Procedure: + + expandLen := uint(256) + seedDst := []byte("TODO") + generatorSeed := []byte("TODO") + generatorDst := []byte("TODO") + + exp := expander.NewExpanderMD(crypto.SHA256, seedDst) + + // 1. v = expand_message(generator_seed, seed_dst, expand_len) + v := exp.Expand(generatorSeed, expandLen) + + lenBuffer := make([]byte, 8) + + // 2. for i in range(1, count): + generators := make([]*pairing.G1, count) + for i := 0; i < count; i++ { + binary.BigEndian.PutUint64(lenBuffer, uint64(i+1)) + expandInput := append(v, lenBuffer...) + // 3. v = expand_message(v || I2OSP(i, 8), seed_dst, expand_len) + // 4. generator_i = hash_to_curve_g1(v, generator_dst) + v = exp.Expand(expandInput, expandLen) + generators[i] = hashToCurveG1(v, generatorDst) + } + + // 5. return (generator_1, ..., generator_count) + return generators +} + +func hashToCurveG1(seed []byte, dst []byte) *pairing.G1 { + p := &pairing.G1{} + p.Hash(seed, dst) + return p +} + +// Signature generation +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-signature-generation-sign +func rawSign(sk *pairing.Scalar, pk []byte, header []byte, messages [][]byte) (Signature, error) { + // Deserialization: + + // 1. L = length(messages) + L := len(messages) + + // 2. (msg_1, ..., msg_L) = messages_to_scalars(messages) + msgs := messagesToScalars(messages) + + // Procedure: + + // 1. (Q_1, H_1, ..., H_L) = create_generators(L+1, PK) + generators := createGenerators(L+1, pk) + + // 2. domain = calculate_domain(PK, Q_1, (H_1, ..., H_L), header) + domain := calculateDomain(pk, generators[0], generators[1:], header) + + // 3. e = hash_to_scalar(serialize((SK, domain, msg_1, ..., msg_L))) + hashInput := []byte{} // XXX(caw) + hashDst := []byte{} + e := hashToScalar(hashInput, hashDst) + + // 4. B = P1 + Q_1 * domain + H_1 * msg_1 + ... + H_L * msg_L + P1 := pairing.G1Generator() + B := &pairing.G1{} + B.Add(P1, generators[0]) + B.ScalarMult(domain, B) + for i := 1; i <= L; i++ { + hi := generators[i] + v := &pairing.G1{} + v.ScalarMult(msgs[i-1], hi) + B.Add(B, v) + } + + // 5. A = B * (1 / (SK + e)) + skE := &pairing.Scalar{} + skE.Add(sk, e) + skEInv := &pairing.Scalar{} + skEInv.Inv(skE) + A := &pairing.G1{} + A.ScalarMult(skEInv, B) + + // 6. return signature_to_octets(A, e) + return Signature{ + A: A, + e: e, + }, nil +} + +func sign(sk *pairing.Scalar, pk []byte, header []byte, messages [][]byte) ([]byte, error) { + sig, err := rawSign(sk, pk, header, messages) + if err != nil { + return nil, err + } + return signatureToOctets(sig) +} + +func signatureToOctets(sig Signature) ([]byte, error) { + Aenc := sig.A.Bytes() + eEnc, err := sig.e.MarshalBinary() + if err != nil { + return nil, err + } + return append(Aenc, eEnc...), nil +} + +func octetsToSignature(data []byte) (Signature, error) { + // 1. expected_len = octet_point_length + octet_scalar_length + // 2. if length(signature_octets) != expected_len, return INVALID + // 3. A_octets = signature_octets[0..(octet_point_length - 1)] + // 4. A = octets_to_point_g1(A_octets) + // 5. if A is INVALID, return INVALID + // 6. if A == Identity_G1, return INVALID + // 7. index = octet_point_length + // 8. end_index = index + octet_scalar_length - 1 + // 9. e = OS2IP(signature_octets[index..end_index]) + // 10. if e = 0 OR e >= r, return INVALID + // 11. return (A, e) + + // XXX(caw): writeme + + return Signature{}, nil +} + +// Signature verification +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-signature-verification-veri +func rawVerify(pk []byte, signature Signature, header []byte, messages [][]byte) error { + // 1. (Q_1, H_1, ..., H_L) = create_generators(L+1, PK) + // 2. domain = calculate_domain(PK, Q_1, (H_1, ..., H_L), header) + // 3. B = P1 + Q_1 * domain + H_1 * msg_1 + ... + H_L * msg_L + // 4. if e(A, W + BP2 * e) * e(B, -BP2) != Identity_GT, return INVALID + // 5. return VALID + + // 1. L = length(messages) + L := len(messages) + + // 2. (msg_1, ..., msg_L) = messages_to_scalars(messages) + msgs := messagesToScalars(messages) + + // Procedure: + + // 1. (Q_1, H_1, ..., H_L) = create_generators(L+1, PK) + generators := createGenerators(L+1, pk) + + // 2. domain = calculate_domain(PK, Q_1, (H_1, ..., H_L), header) + domain := calculateDomain(pk, generators[0], generators[1:], header) + + // 3. B = P1 + Q_1 * domain + H_1 * msg_1 + ... + H_L * msg_L + // XXX(caw): this is shared between sign and verify, so pull it out into a function + P1 := pairing.G1Generator() + B := &pairing.G1{} + B.Add(P1, generators[0]) + B.ScalarMult(domain, B) + for i := 1; i <= L; i++ { + hi := generators[i] + v := &pairing.G1{} + v.ScalarMult(msgs[i-1], hi) + B.Add(B, v) + } + + // 4. if e(A, W + BP2 * e) * e(B, -BP2) != Identity_GT, return INVALID + W := &pairing.G2{} + W.SetBytes(pk) + lg2 := pairing.G2Generator() + lg2.ScalarMult(signature.e, lg2) + lg2.Add(lg2, W) + + rg2 := pairing.G2Generator() + rg2.Neg() + + l := pairing.Pair(signature.A, lg2) + r := pairing.Pair(B, rg2) + target := &pairing.Gt{} + target.Mul(l, r) + if !target.IsIdentity() { + return fmt.Errorf("bbs: invalid signature") + } + + return nil + +} + +func verify(pk, signature, header []byte, messages [][]byte) error { + // Deserialization: + + // 1. signature_result = octets_to_signature(signature) + // 2. if signature_result is INVALID, return INVALID + // 3. (A, e) = signature_result + // 4. W = octets_to_pubkey(PK) + // 5. if W is INVALID, return INVALID + // 6. L = length(messages) + // 7. (msg_1, ..., msg_L) = messages_to_scalars(messages) + + // XXX(caw): invoke rawVerify with the deserialized values + return nil +} + +// Random scalars +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-random-scalars +func calculateRandomScalars(count int) ([]*pairing.Scalar, error) { + scalars := make([]*pairing.Scalar, count) + for i := 0; i < count; i++ { + scalars[i] = &pairing.Scalar{} + err := scalars[i].Random(rand.Reader) + if err != nil { + return nil, err + } + } + return scalars, nil +} + +// XXX(caw): refactor this implementation +func difference(x []int, count int) []int { + if len(x) > count { + panic("invalid difference invocation") + } + indices := make([]int, count-len(x)) + index := 0 + for i := 0; i < count; i++ { + match := false + for _, xi := range x { + if xi == i { + match = true + } + } + if !match { + indices[index] = i + index++ + } + } + + return indices +} + +// Proof generation +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-proof-generation-proofgen +// proof = ProofGen(PK, signature, header, ph, messages, disclosed_indexes) +func rawProofGen(pk []byte, signature Signature, header []byte, ph []byte, messages [][]byte, disclosedIndexes []int) (Proof, error) { + // XXX(caw): need to validate the input disclosedIndexes value to make sure it doesn't have repeated indexes or whatever + + L := len(messages) + msgs := messagesToScalars(messages) + + // ABORT if for i in (i1, ..., iR), i < 1 or i > L + + R := len(disclosedIndexes) + U := L - R + disclosedMsgs := make([]*pairing.Scalar, R) + for i := 0; i < R; i++ { + disclosedMsgs[i] = msgs[disclosedIndexes[i]] + } + + undisclosedIndexes := difference(disclosedIndexes, L) + if len(undisclosedIndexes) != U { + panic("internal error") + } + undisclosedMsgs := make([]*pairing.Scalar, U) + for i := 0; i < U; i++ { + undisclosedMsgs[i] = msgs[undisclosedIndexes[i]] + } + + // Procedure: + + // 1. (Q_1, MsgGenerators) = create_generators(L+1, PK) // XXX(Caw): inconsistent notation (Q1, MsgGEnerators) vs (Q1, H1, .... HL) + // 2. (H_1, ..., H_L) = MsgGenerators + generators := createGenerators(L+1, pk) + msgGenerators := generators[1:] + + // 3. (H_j1, ..., H_jU) = (MsgGenerators[j1], ..., MsgGenerators[jU]) + + undisclosedGenerators := make([]*pairing.G1, U) + for i, index := range undisclosedIndexes { + undisclosedGenerators[i] = msgGenerators[index] + } + + // 4. domain = calculate_domain(PK, Q_1, (H_1, ..., H_L), header) + domain := calculateDomain(pk, generators[0], msgGenerators, header) + + // 5. random_scalars = calculate_random_scalars(3+U) + // 6. (r1, r2, r3, m~_j1, ..., m~_jU) = random_scalars + randomScalars, err := calculateRandomScalars(3 + U) + r1 := randomScalars[0] + r2 := randomScalars[1] + r3 := randomScalars[2] + blinds := randomScalars[3:] + if err != nil { + return Proof{}, err + } + + // 7. B = P1 + Q_1 * domain + H_1 * msg_1 + ... + H_L * msg_L + P1 := pairing.G1Generator() + Q1 := generators[0] + B := &pairing.G1{} + B.ScalarMult(domain, Q1) + B.Add(B, P1) + for i := 1; i <= L; i++ { + hi := generators[i] + v := &pairing.G1{} + v.ScalarMult(msgs[i-1], hi) + B.Add(B, v) + } + + // 8. Abar = A * r1 + Abar := &pairing.G1{} + Abar.ScalarMult(r1, signature.A) + + // 9. Bbar = B * r1 - Abar * e + v := &pairing.G1{} + v.ScalarMult(signature.e, Abar) + v.Neg() // -1 * (Abar * e) + Bbar := &pairing.G1{} + Bbar.ScalarMult(r1, B) + Bbar.Add(Bbar, v) // B * r1 - Abar * e + + // 10. T = Abar * r2 + Bbar * r3 + H_j1 * m~_j1 + ... + H_jU * m~_jU + T1 := &pairing.G1{} + T1.ScalarMult(r2, Abar) // Abar * r2 + T2 := &pairing.G1{} + T2.ScalarMult(r3, Bbar) // Bbar * r3 + T := &pairing.G1{} + T.Add(T1, T2) // (Abar * r2) + (Bbar * r3) + for i := 0; i < U; i++ { + msg := undisclosedGenerators[i] + v := &pairing.G1{} + v.ScalarMult(blinds[i], msg) + T.Add(T, v) + } + + // 11. c = calculate_challenge(Abar, Bbar, T, (i1, ..., iR), + // (msg_i1, ..., msg_iR), domain, ph) + c, err := calculateChallenge(Abar, Bbar, T, disclosedIndexes, disclosedMsgs, domain, ph) + if err != nil { + return Proof{}, fmt.Errorf("bbs: challenge calculate error: %s", err.Error()) + } + + // 12. r4 = - r1^-1 (mod r) + r4 := &pairing.Scalar{} + r4.Inv(r1) // r1^-1 (mod r) + r4.Neg() // - r1^-1 (mod r) + + // 13. r2^ = r2 + e * r4 * c (mod r) + r2h := &pairing.Scalar{} + r2h.Mul(r4, c) // r4 * c (mod r) + r2h.Mul(r2h, signature.e) // e * r4 * c (mod r) + r2h.Add(r2, r2h) // r2 + e * r4 * c (mod r) + + // 14. r3^ = r3 + r4 * c (mod r) + r3h := &pairing.Scalar{} + r3h.Mul(r4, c) // r4 * c (mod r) + r3h.Add(r3, r3h) // r3 + r4 * c (mod r) + + // 15. for j in (j1, ..., jU): m^_j = m~_j + msg_j * c (mod r) + commitments := make([]*pairing.Scalar, U) + for i := 0; i < U; i++ { + t := &pairing.Scalar{} + t.Mul(undisclosedMsgs[i], c) + t.Add(t, blinds[i]) + commitments[i] = t + } + + // 16. proof = (Abar, Bbar, r2^, r3^, (m^_j1, ..., m^_jU), c) + proof := Proof{ + Abar: Abar, + Bbar: Bbar, + r2h: r2h, + r3h: r3h, + commitments: commitments, + c: c, + } + + // 17. return proof_to_octets(proof) + return proof, nil +} + +// Proof verification +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-proof-verification-proofver +func rawProofVerify(pk []byte, proof Proof, header []byte, ph []byte, disclosedMessages [][]byte, disclosedIndexes []int) error { + // Procedure: + + R := len(disclosedIndexes) + U := len(proof.commitments) + L := U + R + + disclosedMsgs := messagesToScalars(disclosedMessages) + + // 1. (Q_1, MsgGenerators) = create_generators(L+1, PK) + generators := createGenerators(L+1, pk) + Q1 := generators[0] + + // 2. (H_1, ..., H_L) = MsgGenerators + msgGenerators := generators[1:] + + // 3. (H_i1, ..., H_iR) = (MsgGenerators[i1], ..., MsgGenerators[iR]) + // XXX(caw): rename this garbage + disclosedGenerators := make([]*pairing.G1, U) + for i, index := range disclosedIndexes { + disclosedGenerators[i] = msgGenerators[index] + } + + // 4. (H_j1, ..., H_jU) = (MsgGenerators[j1], ..., MsgGenerators[jU]) + undisclosedIndexes := difference(disclosedIndexes, L) + // XXX(caw): rename this garbage + undisclosedGenerators := make([]*pairing.G1, U) + for i, index := range undisclosedIndexes { + undisclosedGenerators[i] = msgGenerators[index] + } + + // 5. domain = calculate_domain(PK, Q_1, (H_1, ..., H_L), header) + domain := calculateDomain(pk, generators[0], msgGenerators, header) + + // 6. D = P1 + Q_1 * domain + H_i1 * msg_i1 + ... + H_iR * msg_iR + D1 := &pairing.G1{} + D1.ScalarMult(domain, Q1) // Q_1 * domain + D := &pairing.G1{} + P1 := pairing.G1Generator() + D.Add(D1, P1) // P1 + Q_1 * domain + for i := 0; i < R; i++ { + msg := disclosedMsgs[i] // H_i1 * msg_i1 + v := &pairing.G1{} + v.ScalarMult(msg, disclosedGenerators[i]) + D.Add(D, v) // D += H_i1 * msg_i1 + } + + // 7. T = Abar * r2^ + Bbar * r3^ + H_j1 * m^_j1 + ... + H_jU * m^_jU + T1 := &pairing.G1{} + T1.ScalarMult(proof.r2h, proof.Abar) // Abar * r2^ + T2 := &pairing.G1{} + T2.ScalarMult(proof.r3h, proof.Bbar) // Bbar * r3^ + T := &pairing.G1{} + T.Add(T1, T2) // (Abar * r2^) + (Bbar * r3^) + for i := 0; i < U; i++ { + msg := undisclosedGenerators[i] + v := &pairing.G1{} + v.ScalarMult(proof.commitments[i], msg) + T.Add(T, v) // T += H_j1 * m^_j1 + } + + // 8. T = T + D * c + T1 = &pairing.G1{} + T1.ScalarMult(proof.c, D) + T.Add(T, T1) + + // 9. cv = calculate_challenge(Abar, Bbar, T, (i1, ..., iR), + // (msg_i1, ..., msg_iR), domain, ph) + cv, err := calculateChallenge(proof.Abar, proof.Bbar, T, disclosedIndexes, disclosedMsgs, domain, ph) + if err != nil { + return fmt.Errorf("bbs: challenge calculate error: %s", err.Error()) + } + + // 10. if c != cv, return INVALID + if proof.c.IsEqual(cv) != 1 { + return fmt.Errorf("bbs: invalid proof (challenge mismatch)") + } + + // 11. if e(Abar, W) * e(Bbar, -BP2) != Identity_GT, return INVALID + W := &pairing.G2{} + W.SetBytes(pk) + + rg2 := pairing.G2Generator() + rg2.Neg() + + l := pairing.Pair(proof.Abar, W) + r := pairing.Pair(proof.Bbar, rg2) + target := &pairing.Gt{} + target.Mul(l, r) + if !target.IsIdentity() { + return fmt.Errorf("bbs: invalid proof (pairing failure): %s", target.String()) + } + + // 12. return VALID + return nil +} + +// Hash-to-scalar +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-hash-to-scalar + +func messagesToScalars(messages [][]byte) []*pairing.Scalar { + scalars := make([]*pairing.Scalar, len(messages)) + for i, msg := range messages { + scalars[i] = mapToScalar(msg, i) + } + return scalars +} + +func mapToScalar(msg []byte, index int) *pairing.Scalar { + // dst = ciphersuite_id || "MAP_MSG_TO_SCALAR_AS_HASH_", where ciphersuite_id is defined by the ciphersuite. + // XXX(caw): should MAP_MSG_TO_SCALAR_AS_HASH_ be MAP_TO_SCALAR_ID?, e.g., dst = ciphersuite_id || MAP_TO_SCALAR_ID + return hashToScalar(msg, []byte("TODO")) +} + +// XXX(caw): dst SHOULD NOT be optional with the same domain separation tag used everywhere in the spec +func hashToScalar(msg, dst []byte) *pairing.Scalar { + // XXX(caw): this should just call hash_to_field(msg, 1) directly + + // uniform_bytes = expand_message(msg_octets, dst, expand_len) + // return OS2IP(uniform_bytes) mod r + + expandLength := uint(256) + + exp := expander.NewExpanderMD(crypto.SHA256, dst) + uniformBytes := exp.Expand(msg, expandLength) + scalar := new(big.Int).SetBytes(uniformBytes) + order := new(big.Int).SetBytes(bls12381.Order()) + scalar.Mod(scalar, order) + + result := &bls12381.Scalar{} + result.SetBytes(scalar.Bytes()) + return result +} diff --git a/bbs/bbs_test.go b/bbs/bbs_test.go new file mode 100644 index 000000000..eb3ef3537 --- /dev/null +++ b/bbs/bbs_test.go @@ -0,0 +1,66 @@ +package bbs + +import ( + "testing" +) + +func TestDifference(t *testing.T) { + N := 10 + skipped := []int{0, 1, 5} + expected := []int{2, 3, 4, 6, 7, 8, 9} + diff := difference(skipped, N) + if len(expected) != len(diff) { + t.Fatal("mismatch difference") + } + for i := 0; i < len(diff); i++ { + if expected[i] != diff[i] { + t.Fatal("mismatch difference") + } + } +} + +func TestRoundTrip(t *testing.T) { + ikm := make([]byte, 32) + keyInfo := []byte{} + keyDst := []byte{} + + sk, err := keyGen(ikm, keyInfo, keyDst) + if err != nil { + t.Fatal(err) + } + + header := []byte("test header") + ph := []byte("presentation header") + + messages := make([][]byte, 5) + messages[0] = []byte("hello") + messages[1] = []byte("world") + messages[1] = []byte("foo") + messages[1] = []byte("bar") + messages[1] = []byte("baz") + + sig, err := rawSign(sk, publicKey(sk), header, messages) + if err != nil { + t.Fatal(err) + } + + err = rawVerify(publicKey(sk), sig, header, messages) + if err != nil { + t.Fatal(err) + } + + disclosedIndexes := []int{0, 1} + disclosedMessages := make([][]byte, len(disclosedIndexes)) + for i := 0; i < len(disclosedIndexes); i++ { + disclosedMessages[i] = messages[disclosedIndexes[i]] + } + proof, err := rawProofGen(publicKey(sk), sig, header, ph, messages, disclosedIndexes) + if err != nil { + t.Fatal(err) + } + + err = rawProofVerify(publicKey(sk), proof, header, ph, disclosedMessages, disclosedIndexes) + if err != nil { + t.Fatal(err) + } +} diff --git a/bbs/doc.go b/bbs/doc.go new file mode 100644 index 000000000..5651cbae2 --- /dev/null +++ b/bbs/doc.go @@ -0,0 +1,2 @@ +// Package bbs provides an implementation of the BBS signature scheme +package bbs From 96f8dfe25593837d10a8854cd1fc66aca3ab8622 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Fri, 13 Oct 2023 15:26:02 -0700 Subject: [PATCH 2/6] Fix test vector bugs --- bbs/bbs.go | 141 +++++++++++++++++++++++++++++------------- bbs/bbs_test.go | 161 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+), 44 deletions(-) diff --git a/bbs/bbs.go b/bbs/bbs.go index ab733e5b4..298cf1021 100644 --- a/bbs/bbs.go +++ b/bbs/bbs.go @@ -4,7 +4,6 @@ import ( "crypto" "crypto/rand" "encoding/binary" - "encoding/hex" "fmt" "math/big" @@ -14,18 +13,39 @@ import ( ) var ( - ciphersuiteID = []byte{0x00, 0x01} + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-bls12-381-sha-256 + ciphersuiteID = []byte("BBS_BLS12381G1_XMD:SHA-256_SSWU_RO_H2G_HM2S_") + octetScalarLen = 32 + octetPointLen = 48 + h2cSuite = []byte("BLS12381G1_XMD:SHA-256_SSWU_RO_") + expandLength = uint(48) ) func ciphersuiteString(suffix string) []byte { + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-ciphersuite-id return append(ciphersuiteID, []byte(suffix)...) } +func computeP1() *pairing.G1 { + generatorSeed := ciphersuiteString("BP_MESSAGE_GENERATOR_SEED") + p1 := hashToGenerators(1, generatorSeed) + return p1[0] +} + type Signature struct { A *pairing.G1 e *pairing.Scalar } +func (s Signature) Encode() []byte { + AEnc := s.A.BytesCompressed() + eEnc, err := s.e.MarshalBinary() + if err != nil { + panic(err) + } + return append(AEnc, eEnc...) +} + // (Abar, Bbar, r2^, r3^, (m^_j1, ..., m^_jU), c) type Proof struct { Abar *pairing.G1 @@ -68,8 +88,8 @@ func publicKey(sk *pairing.Scalar) []byte { W := pairing.G2Generator() W.ScalarMult(sk, W) - // XXX(caw): point_to_octets_g2 - return W.Bytes() + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#section-6.2.2-3 + return W.BytesCompressed() } // Serialize @@ -78,6 +98,7 @@ func publicKey(sk *pairing.Scalar) []byte { // Domain calculation // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-domain-calculation +// XXX(caw): this function needs test vectors for the draft func calculateDomain(pk []byte, Q1 *pairing.G1, hPoints []*pairing.G1, header []byte) *pairing.Scalar { // XXX(caw): check for length of header @@ -88,11 +109,9 @@ func calculateDomain(pk []byte, Q1 *pairing.G1, hPoints []*pairing.G1, header [] // 1. dom_array = (L, Q_1, H_1, ..., H_L) // 2. dom_octs = serialize(dom_array) || ciphersuite_id - octets := make([]byte, 0) - octets = append(octets, lenBuffer...) - octets = append(octets, Q1.Bytes()...) + octets := append(lenBuffer, Q1.BytesCompressed()...) for _, hi := range hPoints { - octets = append(octets, hi.Bytes()...) + octets = append(octets, hi.BytesCompressed()...) } octets = append(octets, ciphersuiteID...) @@ -103,7 +122,7 @@ func calculateDomain(pk []byte, Q1 *pairing.G1, hPoints []*pairing.G1, header [] domInput = append(domInput, header...) // 4. return hash_to_scalar(dom_input) - // XXX(caw): this should have an explicit DST + // XXX(caw): this should have an explicit DST and not default to nil return hashToScalar(domInput, nil) } @@ -114,7 +133,7 @@ func encodeInt(x int) []byte { } func concat(x, y []byte) []byte { - fmt.Println(hex.EncodeToString(y)) + // fmt.Println(hex.EncodeToString(y)) return append(x, y...) } @@ -168,25 +187,25 @@ func calculateChallenge(Abar, Bbar, C *pairing.G1, indexArray []int, msgArray [] // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-generators-calculation func createGenerators(count int, pk []byte) []*pairing.G1 { // create_generators(count, PK) := hash_to_generator(count) - return hashToGenerators(count) + generatorSeed := ciphersuiteString("MESSAGE_GENERATOR_SEED") + return hashToGenerators(count, generatorSeed) } -func hashToGenerators(count int) []*pairing.G1 { +func hashToGenerators(count int, generatorSeed []byte) []*pairing.G1 { // ABORT if: // 1. count > 2^64 - 1 // Procedure: - expandLen := uint(256) - seedDst := []byte("TODO") - generatorSeed := []byte("TODO") - generatorDst := []byte("TODO") + seedDst := ciphersuiteString("SIG_GENERATOR_SEED_") + generatorDst := ciphersuiteString("SIG_GENERATOR_DST_") + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-16#section-8.8.1 exp := expander.NewExpanderMD(crypto.SHA256, seedDst) // 1. v = expand_message(generator_seed, seed_dst, expand_len) - v := exp.Expand(generatorSeed, expandLen) + v := exp.Expand(generatorSeed, expandLength) lenBuffer := make([]byte, 8) @@ -197,7 +216,7 @@ func hashToGenerators(count int) []*pairing.G1 { expandInput := append(v, lenBuffer...) // 3. v = expand_message(v || I2OSP(i, 8), seed_dst, expand_len) // 4. generator_i = hash_to_curve_g1(v, generator_dst) - v = exp.Expand(expandInput, expandLen) + v = exp.Expand(expandInput, expandLength) generators[i] = hashToCurveG1(v, generatorDst) } @@ -230,16 +249,32 @@ func rawSign(sk *pairing.Scalar, pk []byte, header []byte, messages [][]byte) (S // 2. domain = calculate_domain(PK, Q_1, (H_1, ..., H_L), header) domain := calculateDomain(pk, generators[0], generators[1:], header) + // e_input = serialize((SK, domain, msg_1, ..., msg_L)) + skEnc, err := sk.MarshalBinary() + if err != nil { + return Signature{}, err + } + domainEnc, err := domain.MarshalBinary() + if err != nil { + return Signature{}, err + } + hashInput := append(skEnc, domainEnc...) + for i := 0; i < L; i++ { + msgEnc, err := msgs[i].MarshalBinary() + if err != nil { + return Signature{}, err + } + hashInput = append(hashInput, msgEnc...) + } + // 3. e = hash_to_scalar(serialize((SK, domain, msg_1, ..., msg_L))) - hashInput := []byte{} // XXX(caw) - hashDst := []byte{} - e := hashToScalar(hashInput, hashDst) + e := hashToScalar(hashInput, nil) // 4. B = P1 + Q_1 * domain + H_1 * msg_1 + ... + H_L * msg_L - P1 := pairing.G1Generator() + P1 := computeP1() B := &pairing.G1{} - B.Add(P1, generators[0]) - B.ScalarMult(domain, B) + B.ScalarMult(domain, generators[0]) // Q_1 * domain + B.Add(P1, B) // P1 + Q_1 * domain for i := 1; i <= L; i++ { hi := generators[i] v := &pairing.G1{} @@ -267,16 +302,7 @@ func sign(sk *pairing.Scalar, pk []byte, header []byte, messages [][]byte) ([]by if err != nil { return nil, err } - return signatureToOctets(sig) -} - -func signatureToOctets(sig Signature) ([]byte, error) { - Aenc := sig.A.Bytes() - eEnc, err := sig.e.MarshalBinary() - if err != nil { - return nil, err - } - return append(Aenc, eEnc...), nil + return sig.Encode(), nil } func octetsToSignature(data []byte) (Signature, error) { @@ -322,10 +348,10 @@ func rawVerify(pk []byte, signature Signature, header []byte, messages [][]byte) // 3. B = P1 + Q_1 * domain + H_1 * msg_1 + ... + H_L * msg_L // XXX(caw): this is shared between sign and verify, so pull it out into a function - P1 := pairing.G1Generator() + P1 := computeP1() B := &pairing.G1{} - B.Add(P1, generators[0]) - B.ScalarMult(domain, B) + B.ScalarMult(domain, generators[0]) // Q_1 * domain + B.Add(P1, B) // P1 + Q_1 * domain for i := 1; i <= L; i++ { hi := generators[i] v := &pairing.G1{} @@ -384,6 +410,30 @@ func calculateRandomScalars(count int) ([]*pairing.Scalar, error) { return scalars, nil } +// Mocked random scalars +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-mocked-random-scalars +func calculateFixedScalars(count int) ([]*pairing.Scalar, error) { + // 1. out_len = expand_len * count + expandLength := uint(256) + outLen := uint(int(expandLength) * count) + seed := []byte{0x00} + + // 2. v = expand_message(SEED, dst, out_len) + dst := ciphersuiteString("MOCK_RANDOM_SCALARS_DST_") + exp := expander.NewExpanderMD(crypto.SHA256, dst) + + uniformBytes := exp.Expand(seed, outLen) + scalars := make([]*pairing.Scalar, count) + for i := 0; i < count; i++ { + start := i * int(expandLength) + end := (i + 1) * int(expandLength) + scalars[i] = &pairing.Scalar{} + scalars[i].SetBytes(uniformBytes[start:end]) + } + + return scalars, nil +} + // XXX(caw): refactor this implementation func difference(x []int, count int) []int { if len(x) > count { @@ -463,7 +513,7 @@ func rawProofGen(pk []byte, signature Signature, header []byte, ph []byte, messa } // 7. B = P1 + Q_1 * domain + H_1 * msg_1 + ... + H_L * msg_L - P1 := pairing.G1Generator() + P1 := computeP1() Q1 := generators[0] B := &pairing.G1{} B.ScalarMult(domain, Q1) @@ -587,7 +637,7 @@ func rawProofVerify(pk []byte, proof Proof, header []byte, ph []byte, disclosedM D1 := &pairing.G1{} D1.ScalarMult(domain, Q1) // Q_1 * domain D := &pairing.G1{} - P1 := pairing.G1Generator() + P1 := computeP1() D.Add(D1, P1) // P1 + Q_1 * domain for i := 0; i < R; i++ { msg := disclosedMsgs[i] // H_i1 * msg_i1 @@ -630,14 +680,14 @@ func rawProofVerify(pk []byte, proof Proof, header []byte, ph []byte, disclosedM // 11. if e(Abar, W) * e(Bbar, -BP2) != Identity_GT, return INVALID W := &pairing.G2{} W.SetBytes(pk) + l := pairing.Pair(proof.Abar, W) // e(Abar, W) rg2 := pairing.G2Generator() rg2.Neg() + r := pairing.Pair(proof.Bbar, rg2) // e(Bbar, -BP2) - l := pairing.Pair(proof.Abar, W) - r := pairing.Pair(proof.Bbar, rg2) target := &pairing.Gt{} - target.Mul(l, r) + target.Mul(l, r) // e(Abar, W) * e(Bbar, -BP2) if !target.IsIdentity() { return fmt.Errorf("bbs: invalid proof (pairing failure): %s", target.String()) } @@ -660,7 +710,8 @@ func messagesToScalars(messages [][]byte) []*pairing.Scalar { func mapToScalar(msg []byte, index int) *pairing.Scalar { // dst = ciphersuite_id || "MAP_MSG_TO_SCALAR_AS_HASH_", where ciphersuite_id is defined by the ciphersuite. // XXX(caw): should MAP_MSG_TO_SCALAR_AS_HASH_ be MAP_TO_SCALAR_ID?, e.g., dst = ciphersuite_id || MAP_TO_SCALAR_ID - return hashToScalar(msg, []byte("TODO")) + dst := ciphersuiteString("MAP_MSG_TO_SCALAR_AS_HASH_") + return hashToScalar(msg, dst) } // XXX(caw): dst SHOULD NOT be optional with the same domain separation tag used everywhere in the spec @@ -670,7 +721,9 @@ func hashToScalar(msg, dst []byte) *pairing.Scalar { // uniform_bytes = expand_message(msg_octets, dst, expand_len) // return OS2IP(uniform_bytes) mod r - expandLength := uint(256) + if dst == nil { + dst = ciphersuiteString("H2S_") + } exp := expander.NewExpanderMD(crypto.SHA256, dst) uniformBytes := exp.Expand(msg, expandLength) diff --git a/bbs/bbs_test.go b/bbs/bbs_test.go index eb3ef3537..301445c27 100644 --- a/bbs/bbs_test.go +++ b/bbs/bbs_test.go @@ -1,9 +1,31 @@ package bbs import ( + "bytes" + "encoding/hex" "testing" ) +func testMessages() [][]byte { + messages := []string{ + "9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02", + "c344136d9ab02da4dd5908bbba913ae6f58c2cc844b802a6f811f5fb075f9b80", + "7372e9daa5ed31e6cd5c825eac1b855e84476a1d94932aa348e07b73", + "77fe97eb97a1ebe2e81e4e3597a3ee740a66e9ef2412472c", + "496694774c5604ab1b2544eababcf0f53278ff50", + "515ae153e22aae04ad16f759e07237b4", + "d183ddc6e2665aa4e2f088af", + "ac55fb33a75909ed", + "96012096", + "", + } + decodedMessages := make([][]byte, len(messages)) + for i := 0; i < len(messages); i++ { + decodedMessages[i] = mustDecodeHex(messages[i]) + } + return decodedMessages +} + func TestDifference(t *testing.T) { N := 10 skipped := []int{0, 1, 5} @@ -19,6 +41,145 @@ func TestDifference(t *testing.T) { } } +func mustDecodeHex(s string) []byte { + x, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return x +} + +func TestP1(t *testing.T) { + p1 := computeP1() + expectedEnc := mustDecodeHex("a8ce256102840821a3e94ea9025e4662b205762f9776b3a766c872b948f1fd225e7c59698588e70d11406d161b4e28c9") + p1Enc := p1.BytesCompressed() + if !bytes.Equal(p1Enc, expectedEnc) { + t.Fatalf("Incorrect P1 computation, got %s, want %s", hex.EncodeToString(p1Enc), hex.EncodeToString(expectedEnc)) + } +} + +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-key-pair-2 +func TestKeyGen(t *testing.T) { + ikm := mustDecodeHex("746869732d49532d6a7573742d616e2d546573742d494b4d2d746f2d67656e65726174652d246528724074232d6b6579") + keyInfo := mustDecodeHex("746869732d49532d736f6d652d6b65792d6d657461646174612d746f2d62652d757365642d696e2d746573742d6b65792d67656e") + expectedSkEnc := mustDecodeHex("60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc") + expectedPkEnc := mustDecodeHex("a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c") + + sk, err := keyGen(ikm, keyInfo, nil) + if err != nil { + t.Fatal(err) + } + skEnc, err := sk.MarshalBinary() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(skEnc, expectedSkEnc) { + t.Fatalf("derived secret key mismatch, got %s, wanted %s", hex.EncodeToString(skEnc), hex.EncodeToString(expectedSkEnc)) + } + + pkEnc := publicKey(sk) + if !bytes.Equal(pkEnc, expectedPkEnc) { + t.Fatalf("derived public key mismatch, got %s, wanted %s", hex.EncodeToString(pkEnc), hex.EncodeToString(expectedPkEnc)) + } +} + +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-map-messages-to-scalars-2 +func TestMessageMap(t *testing.T) { + mappings := []string{ + "1cb5bb86114b34dc438a911617655a1db595abafac92f47c5001799cf624b430", + "154249d503c093ac2df516d4bb88b510d54fd97e8d7121aede420a25d9521952", + "0c7c4c85cdab32e6fdb0de267b16fa3212733d4e3a3f0d0f751657578b26fe22", + "4a196deafee5c23f630156ae13be3e46e53b7e39094d22877b8cba7f14640888", + "34c5ea4f2ba49117015a02c711bb173c11b06b3f1571b88a2952b93d0ed4cf7e", + "4045b39b83055cd57a4d0203e1660800fabe434004dbdc8730c21ce3f0048b08", + "064621da4377b6b1d05ecc37cf3b9dfc94b9498d7013dc5c4a82bf3bb1750743", + "34ac9196ace0a37e147e32319ea9b3d8cc7d21870d3c3ba071246859cca49b02", + "57eb93f417c43200e9784fa5ea5a59168d3dbc38df707a13bb597c871b2a5f74", + "08e3afeb2b4f2b5f907924ef42856616e6f2d5f1fb373736db1cca32707a7d16", + } + decodedMappings := make([][]byte, len(mappings)) + for i := 0; i < len(mappings); i++ { + decodedMappings[i] = mustDecodeHex(mappings[i]) + } + + messages := testMessages() + scalars := messagesToScalars(messages) + for i := 0; i < len(scalars); i++ { + scalarEnc, err := scalars[i].MarshalBinary() + if err != nil { + panic(err) + } + if !bytes.Equal(scalarEnc, decodedMappings[i]) { + t.Fatalf("incorrect message-to-scalar mapping for index %d, got %s, wanted %s", i, hex.EncodeToString(scalarEnc), hex.EncodeToString(decodedMappings[i])) + } + } +} + +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-message-generators +func TestMessageGenerators(t *testing.T) { + testGenerators := []string{ + "a9ec65b70a7fbe40c874c9eb041c2cb0a7af36ccec1bea48fa2ba4c2eb67ef7f9ecb17ed27d38d27cdeddff44c8137be", + "98cd5313283aaf5db1b3ba8611fe6070d19e605de4078c38df36019fbaad0bd28dd090fd24ed27f7f4d22d5ff5dea7d4", + "a31fbe20c5c135bcaa8d9fc4e4ac665cc6db0226f35e737507e803044093f37697a9d452490a970eea6f9ad6c3dcaa3a", + "b479263445f4d2108965a9086f9d1fdc8cde77d14a91c856769521ad3344754cc5ce90d9bc4c696dffbc9ef1d6ad1b62", + "ac0401766d2128d4791d922557c7b4d1ae9a9b508ce266575244a8d6f32110d7b0b7557b77604869633bb49afbe20035", + "b95d2898370ebc542857746a316ce32fa5151c31f9b57915e308ee9d1de7db69127d919e984ea0747f5223821b596335", + "8f19359ae6ee508157492c06765b7df09e2e5ad591115742f2de9c08572bb2845cbf03fd7e23b7f031ed9c7564e52f39", + "abc914abe2926324b2c848e8a411a2b6df18cbe7758db8644145fefb0bf0a2d558a8c9946bd35e00c69d167aadf304c1", + "80755b3eb0dd4249cbefd20f177cee88e0761c066b71794825c9997b551f24051c352567ba6c01e57ac75dff763eaa17", + "82701eb98070728e1769525e73abff1783cedc364adb20c05c897a62f2ab2927f86f118dcb7819a7b218d8f3fee4bd7f", + "a1f229540474f4d6f1134761b92b788128c7ac8dc9b0c52d59493132679673032ac7db3fb3d79b46b13c1c41ee495bca", + } + decodedGenerators := make([][]byte, len(testGenerators)) + for i := 0; i < len(testGenerators); i++ { + decodedGenerators[i] = mustDecodeHex(testGenerators[i]) + } + + generators := createGenerators(len(testGenerators), nil) + generatorEnc := generators[0].BytesCompressed() + if !bytes.Equal(generatorEnc, decodedGenerators[0]) { + t.Fatalf("incorrect Q1 generator, got %s, wanted %s", hex.EncodeToString(generatorEnc), hex.EncodeToString(decodedGenerators[0])) + } + for i := 1; i < len(generators); i++ { + generatorEnc := generators[i].BytesCompressed() + if !bytes.Equal(generatorEnc, decodedGenerators[i]) { + t.Fatalf("incorrect generator for index %d, got %s, wanted %s", i, hex.EncodeToString(generatorEnc), hex.EncodeToString(decodedGenerators[i])) + } + } +} + +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-valid-single-message-signatu +func TestSingleMessageSignature(t *testing.T) { + header := mustDecodeHex("11223344556677889900aabbccddeeff") + message := mustDecodeHex("9872ad089e452c7b6e283dfac2a80d58e8d0ff71cc4d5e310a1debdda4a45f02") + messages := [][]byte{message} + expectedSigEnc := mustDecodeHex("88c0eb3bc1d97610c3a66d8a3a73f260f95a3028bccf7fff7d9851e2acd9f3f32fdf58a5b34d12df8177adf37aa318a20f72be7d37a8e8d8441d1bc0bc75543c681bf061ce7e7f6091fe78c1cb8af103") + + ikm := mustDecodeHex("746869732d49532d6a7573742d616e2d546573742d494b4d2d746f2d67656e65726174652d246528724074232d6b6579") + keyInfo := mustDecodeHex("746869732d49532d736f6d652d6b65792d6d657461646174612d746f2d62652d757365642d696e2d746573742d6b65792d67656e") + + sk, err := keyGen(ikm, keyInfo, nil) + if err != nil { + t.Fatal(err) + } + + sig, err := rawSign(sk, publicKey(sk), header, messages) + if err != nil { + t.Fatal(err) + } + + // sigEEnc, err := sig.e.MarshalBinary() + // if err != nil { + // t.Fatal(err) + // } + // fmt.Println(hex.EncodeToString(sigEEnc)) + + sigEnc := sig.Encode() + if !bytes.Equal(sigEnc, expectedSigEnc) { + t.Fatalf("incorrect signature, got %s, wanted %s", hex.EncodeToString(sigEnc), hex.EncodeToString(expectedSigEnc)) + } +} + func TestRoundTrip(t *testing.T) { ikm := make([]byte, 32) keyInfo := []byte{} From a00adb5f50f03452dfdc0565fe8ce3f2f52968b3 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Fri, 13 Oct 2023 15:58:15 -0700 Subject: [PATCH 3/6] Add multi-message test --- bbs/bbs.go | 67 ++++++++++++++++++++++++++++--------------------- bbs/bbs_test.go | 29 +++++++++++++++++---- 2 files changed, 63 insertions(+), 33 deletions(-) diff --git a/bbs/bbs.go b/bbs/bbs.go index 298cf1021..82d57c4cb 100644 --- a/bbs/bbs.go +++ b/bbs/bbs.go @@ -46,6 +46,45 @@ func (s Signature) Encode() []byte { return append(AEnc, eEnc...) } +// XXX(caw): rename this function +func octetsToSignature(data []byte) (Signature, error) { + // 1. expected_len = octet_point_length + octet_scalar_length + expectedLen := octetPointLen + octetScalarLen + // 2. if length(signature_octets) != expected_len, return INVALID + if len(data) != expectedLen { + return Signature{}, fmt.Errorf("bbs: malformed signature") + } + + // 3. A_octets = signature_octets[0..(octet_point_length - 1)] + // 4. A = octets_to_point_g1(A_octets) + AOctets := data[0:octetPointLen] + A := &pairing.G1{} + A.SetBytes(AOctets) + + // 5. if A is INVALID, return INVALID + if !A.IsOnG1() { + return Signature{}, fmt.Errorf("bbs: invalid A signature component (not in G1)") + } + // 6. if A == Identity_G1, return INVALID + if A.IsIdentity() { + return Signature{}, fmt.Errorf("bbs: invalid A signature component (identity element)") + } + + // 7. index = octet_point_length + // 8. end_index = index + octet_scalar_length - 1 + // 9. e = OS2IP(signature_octets[index..end_index]) + e := &pairing.Scalar{} + e.SetBytes(data[octetPointLen:]) + + // 10. if e = 0 OR e >= r, return INVALID + // 11. return (A, e) + + return Signature{ + A: A, + e: e, + }, nil +} + // (Abar, Bbar, r2^, r3^, (m^_j1, ..., m^_jU), c) type Proof struct { Abar *pairing.G1 @@ -92,10 +131,6 @@ func publicKey(sk *pairing.Scalar) []byte { return W.BytesCompressed() } -// Serialize -// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-serialize -// XXX(caw): this function feels too complicated and should instead be replaced separate serializeFunctions invoked directly - // Domain calculation // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-domain-calculation // XXX(caw): this function needs test vectors for the draft @@ -305,33 +340,9 @@ func sign(sk *pairing.Scalar, pk []byte, header []byte, messages [][]byte) ([]by return sig.Encode(), nil } -func octetsToSignature(data []byte) (Signature, error) { - // 1. expected_len = octet_point_length + octet_scalar_length - // 2. if length(signature_octets) != expected_len, return INVALID - // 3. A_octets = signature_octets[0..(octet_point_length - 1)] - // 4. A = octets_to_point_g1(A_octets) - // 5. if A is INVALID, return INVALID - // 6. if A == Identity_G1, return INVALID - // 7. index = octet_point_length - // 8. end_index = index + octet_scalar_length - 1 - // 9. e = OS2IP(signature_octets[index..end_index]) - // 10. if e = 0 OR e >= r, return INVALID - // 11. return (A, e) - - // XXX(caw): writeme - - return Signature{}, nil -} - // Signature verification // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-signature-verification-veri func rawVerify(pk []byte, signature Signature, header []byte, messages [][]byte) error { - // 1. (Q_1, H_1, ..., H_L) = create_generators(L+1, PK) - // 2. domain = calculate_domain(PK, Q_1, (H_1, ..., H_L), header) - // 3. B = P1 + Q_1 * domain + H_1 * msg_1 + ... + H_L * msg_L - // 4. if e(A, W + BP2 * e) * e(B, -BP2) != Identity_GT, return INVALID - // 5. return VALID - // 1. L = length(messages) L := len(messages) diff --git a/bbs/bbs_test.go b/bbs/bbs_test.go index 301445c27..878f7d4e9 100644 --- a/bbs/bbs_test.go +++ b/bbs/bbs_test.go @@ -168,11 +168,30 @@ func TestSingleMessageSignature(t *testing.T) { t.Fatal(err) } - // sigEEnc, err := sig.e.MarshalBinary() - // if err != nil { - // t.Fatal(err) - // } - // fmt.Println(hex.EncodeToString(sigEEnc)) + sigEnc := sig.Encode() + if !bytes.Equal(sigEnc, expectedSigEnc) { + t.Fatalf("incorrect signature, got %s, wanted %s", hex.EncodeToString(sigEnc), hex.EncodeToString(expectedSigEnc)) + } +} + +// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-valid-multi-message-signatur +func TestMultiMessageSignature(t *testing.T) { + header := mustDecodeHex("11223344556677889900aabbccddeeff") + messages := testMessages() + expectedSigEnc := mustDecodeHex("895cd9c0ccb9aca4de913218655346d718711472f2bf1f3e68916de106a0d93cf2f47200819b45920bbda541db2d91480665df253fedab2843055bdc02535d83baddbbb2803ec3808e074f71f199751e") + + ikm := mustDecodeHex("746869732d49532d6a7573742d616e2d546573742d494b4d2d746f2d67656e65726174652d246528724074232d6b6579") + keyInfo := mustDecodeHex("746869732d49532d736f6d652d6b65792d6d657461646174612d746f2d62652d757365642d696e2d746573742d6b65792d67656e") + + sk, err := keyGen(ikm, keyInfo, nil) + if err != nil { + t.Fatal(err) + } + + sig, err := rawSign(sk, publicKey(sk), header, messages) + if err != nil { + t.Fatal(err) + } sigEnc := sig.Encode() if !bytes.Equal(sigEnc, expectedSigEnc) { From 5007a769fa44616133b0316d86b0c8435129c6be Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Fri, 13 Oct 2023 16:20:47 -0700 Subject: [PATCH 4/6] Start filling out high-level API --- bbs/bbs.go | 109 +++++++++++++++++++++++++++++++++--------------- bbs/bbs_test.go | 75 ++++++++++++++++++++++++++------- 2 files changed, 134 insertions(+), 50 deletions(-) diff --git a/bbs/bbs.go b/bbs/bbs.go index 82d57c4cb..44c849762 100644 --- a/bbs/bbs.go +++ b/bbs/bbs.go @@ -46,8 +46,7 @@ func (s Signature) Encode() []byte { return append(AEnc, eEnc...) } -// XXX(caw): rename this function -func octetsToSignature(data []byte) (Signature, error) { +func UnmarshalSignature(data []byte) (Signature, error) { // 1. expected_len = octet_point_length + octet_scalar_length expectedLen := octetPointLen + octetScalarLen // 2. if length(signature_octets) != expected_len, return INVALID @@ -85,7 +84,6 @@ func octetsToSignature(data []byte) (Signature, error) { }, nil } -// (Abar, Bbar, r2^, r3^, (m^_j1, ..., m^_jU), c) type Proof struct { Abar *pairing.G1 Bbar *pairing.G1 @@ -95,14 +93,66 @@ type Proof struct { c *pairing.Scalar } +func (p Proof) Encode() []byte { + ABarEnc := p.Abar.BytesCompressed() + BBarEnc := p.Bbar.BytesCompressed() + r2hEnc, err := p.r2h.MarshalBinary() + if err != nil { + panic(err) + } + r3hEnc, err := p.r3h.MarshalBinary() + if err != nil { + panic(err) + } + cEnc, err := p.c.MarshalBinary() + if err != nil { + panic(err) + } + + result := append(ABarEnc, BBarEnc...) + result = append(result, r2hEnc...) + result = append(result, r3hEnc...) + for i := 0; i < len(p.commitments); i++ { + commitmentEnc, err := p.commitments[i].MarshalBinary() + if err != nil { + panic(err) + } + result = append(result, commitmentEnc...) + } + result = append(result, cEnc...) + return result +} + +type SecretKey struct { + sk *pairing.Scalar +} + +type PublicKey []byte + +func (s SecretKey) Public() PublicKey { + W := pairing.G2Generator() + W.ScalarMult(s.sk, W) + + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#section-6.2.2-3 + return W.BytesCompressed() +} + +func (s SecretKey) Encode() []byte { + enc, err := s.sk.MarshalBinary() + if err != nil { + panic(err) + } + return enc +} + // Key generation // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-key-generation-operations -func keyGen(ikm []byte, keyInfo, keyDst []byte) (*pairing.Scalar, error) { +func KeyGen(ikm []byte, keyInfo, keyDst []byte) (SecretKey, error) { if len(ikm) < 32 { - return nil, fmt.Errorf("bbs: invalid keyGen ikm") + return SecretKey{}, fmt.Errorf("bbs: invalid keyGen ikm") } if len(keyInfo) > 65535 { - return nil, fmt.Errorf("bbs: invalid keyGen keyInfo") + return SecretKey{}, fmt.Errorf("bbs: invalid keyGen keyInfo") } if keyDst == nil { // keyDst = ciphersuite_id || "KEYGEN_DST_" @@ -120,15 +170,9 @@ func keyGen(ikm []byte, keyInfo, keyDst []byte) (*pairing.Scalar, error) { // if SK is INVALID, return INVALID // XXX(caw): what does it mean for SK to be invalid if hash_to_scalar never returns an invalid scalar? - return sk, nil -} - -func publicKey(sk *pairing.Scalar) []byte { - W := pairing.G2Generator() - W.ScalarMult(sk, W) - - // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#section-6.2.2-3 - return W.BytesCompressed() + return SecretKey{ + sk: sk, + }, nil } // Domain calculation @@ -168,7 +212,6 @@ func encodeInt(x int) []byte { } func concat(x, y []byte) []byte { - // fmt.Println(hex.EncodeToString(y)) return append(x, y...) } @@ -214,8 +257,8 @@ func calculateChallenge(Abar, Bbar, C *pairing.G1, indexArray []int, msgArray [] challengeInput = concat(challengeInput, encodeInt(len(ph))) challengeInput = concat(challengeInput, ph) - dst := []byte("TODO") - return hashToScalar(challengeInput, dst), nil + // XXX(caw): this should have an explicit DST + return hashToScalar(challengeInput, nil), nil } // Generators calculation @@ -230,6 +273,9 @@ func hashToGenerators(count int, generatorSeed []byte) []*pairing.G1 { // ABORT if: // 1. count > 2^64 - 1 + if uint64(count) > ^uint64(0) { + panic("invalid invocation") + } // Procedure: @@ -267,7 +313,7 @@ func hashToCurveG1(seed []byte, dst []byte) *pairing.G1 { // Signature generation // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-signature-generation-sign -func rawSign(sk *pairing.Scalar, pk []byte, header []byte, messages [][]byte) (Signature, error) { +func rawSign(sk SecretKey, pk []byte, header []byte, messages [][]byte) (Signature, error) { // Deserialization: // 1. L = length(messages) @@ -285,7 +331,7 @@ func rawSign(sk *pairing.Scalar, pk []byte, header []byte, messages [][]byte) (S domain := calculateDomain(pk, generators[0], generators[1:], header) // e_input = serialize((SK, domain, msg_1, ..., msg_L)) - skEnc, err := sk.MarshalBinary() + skEnc, err := sk.sk.MarshalBinary() if err != nil { return Signature{}, err } @@ -319,7 +365,7 @@ func rawSign(sk *pairing.Scalar, pk []byte, header []byte, messages [][]byte) (S // 5. A = B * (1 / (SK + e)) skE := &pairing.Scalar{} - skE.Add(sk, e) + skE.Add(sk.sk, e) skEInv := &pairing.Scalar{} skEInv.Inv(skE) A := &pairing.G1{} @@ -332,7 +378,7 @@ func rawSign(sk *pairing.Scalar, pk []byte, header []byte, messages [][]byte) (S }, nil } -func sign(sk *pairing.Scalar, pk []byte, header []byte, messages [][]byte) ([]byte, error) { +func Sign(sk SecretKey, pk []byte, header []byte, messages [][]byte) ([]byte, error) { sig, err := rawSign(sk, pk, header, messages) if err != nil { return nil, err @@ -392,19 +438,14 @@ func rawVerify(pk []byte, signature Signature, header []byte, messages [][]byte) } -func verify(pk, signature, header []byte, messages [][]byte) error { - // Deserialization: - +func Verify(pk PublicKey, signature, header []byte, messages [][]byte) error { // 1. signature_result = octets_to_signature(signature) - // 2. if signature_result is INVALID, return INVALID - // 3. (A, e) = signature_result - // 4. W = octets_to_pubkey(PK) - // 5. if W is INVALID, return INVALID - // 6. L = length(messages) - // 7. (msg_1, ..., msg_L) = messages_to_scalars(messages) - - // XXX(caw): invoke rawVerify with the deserialized values - return nil + sig, err := UnmarshalSignature(signature) + if err != nil { + return err + } + + return rawVerify(pk, sig, header, messages) } // Random scalars diff --git a/bbs/bbs_test.go b/bbs/bbs_test.go index 878f7d4e9..a9ce3c5eb 100644 --- a/bbs/bbs_test.go +++ b/bbs/bbs_test.go @@ -65,19 +65,16 @@ func TestKeyGen(t *testing.T) { expectedSkEnc := mustDecodeHex("60e55110f76883a13d030b2f6bd11883422d5abde717569fc0731f51237169fc") expectedPkEnc := mustDecodeHex("a820f230f6ae38503b86c70dc50b61c58a77e45c39ab25c0652bbaa8fa136f2851bd4781c9dcde39fc9d1d52c9e60268061e7d7632171d91aa8d460acee0e96f1e7c4cfb12d3ff9ab5d5dc91c277db75c845d649ef3c4f63aebc364cd55ded0c") - sk, err := keyGen(ikm, keyInfo, nil) - if err != nil { - t.Fatal(err) - } - skEnc, err := sk.MarshalBinary() + sk, err := KeyGen(ikm, keyInfo, nil) if err != nil { t.Fatal(err) } + skEnc := sk.Encode() if !bytes.Equal(skEnc, expectedSkEnc) { t.Fatalf("derived secret key mismatch, got %s, wanted %s", hex.EncodeToString(skEnc), hex.EncodeToString(expectedSkEnc)) } - pkEnc := publicKey(sk) + pkEnc := sk.Public() if !bytes.Equal(pkEnc, expectedPkEnc) { t.Fatalf("derived public key mismatch, got %s, wanted %s", hex.EncodeToString(pkEnc), hex.EncodeToString(expectedPkEnc)) } @@ -158,12 +155,12 @@ func TestSingleMessageSignature(t *testing.T) { ikm := mustDecodeHex("746869732d49532d6a7573742d616e2d546573742d494b4d2d746f2d67656e65726174652d246528724074232d6b6579") keyInfo := mustDecodeHex("746869732d49532d736f6d652d6b65792d6d657461646174612d746f2d62652d757365642d696e2d746573742d6b65792d67656e") - sk, err := keyGen(ikm, keyInfo, nil) + sk, err := KeyGen(ikm, keyInfo, nil) if err != nil { t.Fatal(err) } - sig, err := rawSign(sk, publicKey(sk), header, messages) + sig, err := rawSign(sk, sk.Public(), header, messages) if err != nil { t.Fatal(err) } @@ -183,12 +180,12 @@ func TestMultiMessageSignature(t *testing.T) { ikm := mustDecodeHex("746869732d49532d6a7573742d616e2d546573742d494b4d2d746f2d67656e65726174652d246528724074232d6b6579") keyInfo := mustDecodeHex("746869732d49532d736f6d652d6b65792d6d657461646174612d746f2d62652d757365642d696e2d746573742d6b65792d67656e") - sk, err := keyGen(ikm, keyInfo, nil) + sk, err := KeyGen(ikm, keyInfo, nil) if err != nil { t.Fatal(err) } - sig, err := rawSign(sk, publicKey(sk), header, messages) + sig, err := rawSign(sk, sk.Public(), header, messages) if err != nil { t.Fatal(err) } @@ -199,12 +196,12 @@ func TestMultiMessageSignature(t *testing.T) { } } -func TestRoundTrip(t *testing.T) { +func TestRawRoundTrip(t *testing.T) { ikm := make([]byte, 32) keyInfo := []byte{} keyDst := []byte{} - sk, err := keyGen(ikm, keyInfo, keyDst) + sk, err := KeyGen(ikm, keyInfo, keyDst) if err != nil { t.Fatal(err) } @@ -219,12 +216,12 @@ func TestRoundTrip(t *testing.T) { messages[1] = []byte("bar") messages[1] = []byte("baz") - sig, err := rawSign(sk, publicKey(sk), header, messages) + sig, err := rawSign(sk, sk.Public(), header, messages) if err != nil { t.Fatal(err) } - err = rawVerify(publicKey(sk), sig, header, messages) + err = rawVerify(sk.Public(), sig, header, messages) if err != nil { t.Fatal(err) } @@ -234,13 +231,59 @@ func TestRoundTrip(t *testing.T) { for i := 0; i < len(disclosedIndexes); i++ { disclosedMessages[i] = messages[disclosedIndexes[i]] } - proof, err := rawProofGen(publicKey(sk), sig, header, ph, messages, disclosedIndexes) + proof, err := rawProofGen(sk.Public(), sig, header, ph, messages, disclosedIndexes) if err != nil { t.Fatal(err) } - err = rawProofVerify(publicKey(sk), proof, header, ph, disclosedMessages, disclosedIndexes) + err = rawProofVerify(sk.Public(), proof, header, ph, disclosedMessages, disclosedIndexes) if err != nil { t.Fatal(err) } } + +func TestRoundTrip(t *testing.T) { + ikm := make([]byte, 32) + keyInfo := []byte{} + keyDst := []byte{} + + sk, err := KeyGen(ikm, keyInfo, keyDst) + if err != nil { + t.Fatal(err) + } + + header := []byte("test header") + + messages := make([][]byte, 5) + messages[0] = []byte("hello") + messages[1] = []byte("world") + messages[1] = []byte("foo") + messages[1] = []byte("bar") + messages[1] = []byte("baz") + + sig, err := Sign(sk, sk.Public(), header, messages) + if err != nil { + t.Fatal(err) + } + + err = Verify(sk.Public(), sig, header, messages) + if err != nil { + t.Fatal(err) + } + + // ph := []byte("presentation header") + // disclosedIndexes := []int{0, 1} + // disclosedMessages := make([][]byte, len(disclosedIndexes)) + // for i := 0; i < len(disclosedIndexes); i++ { + // disclosedMessages[i] = messages[disclosedIndexes[i]] + // } + // proof, err := rawProofGen(sk.Public(), sig, header, ph, messages, disclosedIndexes) + // if err != nil { + // t.Fatal(err) + // } + + // err = rawProofVerify(sk.Public(), proof, header, ph, disclosedMessages, disclosedIndexes) + // if err != nil { + // t.Fatal(err) + // } +} From 13e767c5c3da5ef4e3df65dfaf7589ccabc0d1e1 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Sun, 15 Oct 2023 19:08:29 -0700 Subject: [PATCH 5/6] Proof encoding and decoding --- bbs/bbs.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++- bbs/bbs_test.go | 30 +++++++------- 2 files changed, 120 insertions(+), 17 deletions(-) diff --git a/bbs/bbs.go b/bbs/bbs.go index 44c849762..b3456fc4e 100644 --- a/bbs/bbs.go +++ b/bbs/bbs.go @@ -123,6 +123,88 @@ func (p Proof) Encode() []byte { return result } +func unmarshalProof(data []byte) (Proof, error) { + proofLenFloor := 2*octetPointLen + 3*octetScalarLen + if len(data) < proofLenFloor { + return Proof{}, fmt.Errorf("bbs: malformed proof") + } + + // // Points (i.e., (Abar, Bbar) in ProofGen) de-serialization. + // 3. index = 0 + // 4. for i in range(0, 1): + // 5. end_index = index + octet_point_length - 1 + // 6. A_i = octets_to_point_g1(proof_octets[index..end_index]) + // 7. if A_i is INVALID or Identity_G1, return INVALID + // 8. index += octet_point_length + // index := 0 + + index := 0 + + // Abar + octets := data[index : index+octetPointLen] + Abar := &pairing.G1{} + Abar.SetBytes(octets) + index += octetPointLen + + // Bbar + octets = data[index : index+octetPointLen] + Bbar := &pairing.G1{} + Bbar.SetBytes(octets) + index += octetPointLen + + // Scalars (i.e., (r2^, r3^, m^_j1, ..., m^_jU, c) in + // r2h and r3h + r2h := &pairing.Scalar{} + r2h.SetBytes(data[index : index+octetScalarLen]) + index += octetScalarLen + r3h := &pairing.Scalar{} + r3h.SetBytes(data[index : index+octetScalarLen]) + index += octetScalarLen + + i := 0 + scalars := make([]*pairing.Scalar, 0) + for { + if index < len(data) { + // XXX(caw): need to check if there is enough data + scalars = append(scalars, &pairing.Scalar{}) + scalars[i].SetBytes(data[index : index+octetScalarLen]) + index += octetScalarLen + i += 1 + } else { + if index != len(data) { + return Proof{}, fmt.Errorf("bbs: malformed proof") + } + if len(scalars) < 3 { + return Proof{}, fmt.Errorf("bbs: malformed proof") + } + return Proof{ + Abar: Abar, + Bbar: Bbar, + r2h: r2h, + r3h: r3h, + commitments: scalars[0 : len(scalars)-1], + c: scalars[len(scalars)-1], + }, nil + } + } + + // // ProofGen) de-serialization. + // 9. j = 0 + // 10. while index < length(proof_octets): + // 11. end_index = index + octet_scalar_length - 1 + // 12. s_j = OS2IP(proof_octets[index..end_index]) + // 13. if s_j = 0 or if s_j >= r, return INVALID + // 14. index += octet_scalar_length + // 15. j += 1 + + // 16. if index != length(proof_octets), return INVALID + // 17. msg_commitments = () + // 18. If j > 3, set msg_commitments = (s_2, ..., s_(j-2)) + // 19. return (A_0, A_1, s_0, s_1, msg_commitments, s_(j-1)) + + // return Proof{}, nil +} + type SecretKey struct { sk *pairing.Scalar } @@ -435,7 +517,6 @@ func rawVerify(pk []byte, signature Signature, header []byte, messages [][]byte) } return nil - } func Verify(pk PublicKey, signature, header []byte, messages [][]byte) error { @@ -511,7 +592,6 @@ func difference(x []int, count int) []int { // Proof generation // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-proof-generation-proofgen -// proof = ProofGen(PK, signature, header, ph, messages, disclosed_indexes) func rawProofGen(pk []byte, signature Signature, header []byte, ph []byte, messages [][]byte, disclosedIndexes []int) (Proof, error) { // XXX(caw): need to validate the input disclosedIndexes value to make sure it doesn't have repeated indexes or whatever @@ -649,6 +729,20 @@ func rawProofGen(pk []byte, signature Signature, header []byte, ph []byte, messa return proof, nil } +func ProofGen(pk PublicKey, signature []byte, header []byte, ph []byte, messages [][]byte, disclosedIndexes []int) ([]byte, error) { + // func rawProofGen(pk []byte, signature Signature, header []byte, ph []byte, messages [][]byte, disclosedIndexes []int) (Proof, error) { + sig, err := UnmarshalSignature(signature) + if err != nil { + return nil, err + } + proof, err := rawProofGen(pk, sig, header, ph, messages, disclosedIndexes) + if err != nil { + return nil, err + } + + return proof.Encode(), nil +} + // Proof verification // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-proof-verification-proofver func rawProofVerify(pk []byte, proof Proof, header []byte, ph []byte, disclosedMessages [][]byte, disclosedIndexes []int) error { @@ -748,6 +842,15 @@ func rawProofVerify(pk []byte, proof Proof, header []byte, ph []byte, disclosedM return nil } +func ProofVerify(pk, proof, header, ph []byte, disclosedMessages [][]byte, disclosedIndexes []int) error { + // func rawProofVerify(pk []byte, proof Proof, header []byte, ph []byte, disclosedMessages [][]byte, disclosedIndexes []int) error { + p, err := unmarshalProof(proof) + if err != nil { + return err + } + return rawProofVerify(pk, p, header, ph, disclosedMessages, disclosedIndexes) +} + // Hash-to-scalar // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-hash-to-scalar diff --git a/bbs/bbs_test.go b/bbs/bbs_test.go index a9ce3c5eb..0fedee0a6 100644 --- a/bbs/bbs_test.go +++ b/bbs/bbs_test.go @@ -271,19 +271,19 @@ func TestRoundTrip(t *testing.T) { t.Fatal(err) } - // ph := []byte("presentation header") - // disclosedIndexes := []int{0, 1} - // disclosedMessages := make([][]byte, len(disclosedIndexes)) - // for i := 0; i < len(disclosedIndexes); i++ { - // disclosedMessages[i] = messages[disclosedIndexes[i]] - // } - // proof, err := rawProofGen(sk.Public(), sig, header, ph, messages, disclosedIndexes) - // if err != nil { - // t.Fatal(err) - // } - - // err = rawProofVerify(sk.Public(), proof, header, ph, disclosedMessages, disclosedIndexes) - // if err != nil { - // t.Fatal(err) - // } + ph := []byte("presentation header") + disclosedIndexes := []int{0, 1} + disclosedMessages := make([][]byte, len(disclosedIndexes)) + for i := 0; i < len(disclosedIndexes); i++ { + disclosedMessages[i] = messages[disclosedIndexes[i]] + } + proof, err := ProofGen(sk.Public(), sig, header, ph, messages, disclosedIndexes) + if err != nil { + t.Fatal(err) + } + + err = ProofVerify(sk.Public(), proof, header, ph, disclosedMessages, disclosedIndexes) + if err != nil { + t.Fatal(err) + } } From cfaea672058fc613a86717cb9d0e315d70d55cc2 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Wed, 18 Oct 2023 10:29:19 -0700 Subject: [PATCH 6/6] Cleanup go fmt warnings --- bbs/bbs.go | 64 +++++++++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/bbs/bbs.go b/bbs/bbs.go index b3456fc4e..329172e79 100644 --- a/bbs/bbs.go +++ b/bbs/bbs.go @@ -17,7 +17,6 @@ var ( ciphersuiteID = []byte("BBS_BLS12381G1_XMD:SHA-256_SSWU_RO_H2G_HM2S_") octetScalarLen = 32 octetPointLen = 48 - h2cSuite = []byte("BLS12381G1_XMD:SHA-256_SSWU_RO_") expandLength = uint(48) ) @@ -58,7 +57,10 @@ func UnmarshalSignature(data []byte) (Signature, error) { // 4. A = octets_to_point_g1(A_octets) AOctets := data[0:octetPointLen] A := &pairing.G1{} - A.SetBytes(AOctets) + err := A.SetBytes(AOctets) + if err != nil { + return Signature{}, fmt.Errorf("bbs: malformed signature") + } // 5. if A is INVALID, return INVALID if !A.IsOnG1() { @@ -143,13 +145,19 @@ func unmarshalProof(data []byte) (Proof, error) { // Abar octets := data[index : index+octetPointLen] Abar := &pairing.G1{} - Abar.SetBytes(octets) + err := Abar.SetBytes(octets) + if err != nil { + return Proof{}, fmt.Errorf("bbs: malformed proof") + } index += octetPointLen // Bbar octets = data[index : index+octetPointLen] Bbar := &pairing.G1{} - Bbar.SetBytes(octets) + err = Bbar.SetBytes(octets) + if err != nil { + return Proof{}, fmt.Errorf("bbs: malformed proof") + } index += octetPointLen // Scalars (i.e., (r2^, r3^, m^_j1, ..., m^_jU, c) in @@ -345,7 +353,7 @@ func calculateChallenge(Abar, Bbar, C *pairing.G1, indexArray []int, msgArray [] // Generators calculation // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-generators-calculation -func createGenerators(count int, pk []byte) []*pairing.G1 { +func createGenerators(count int) []*pairing.G1 { // create_generators(count, PK) := hash_to_generator(count) generatorSeed := ciphersuiteString("MESSAGE_GENERATOR_SEED") return hashToGenerators(count, generatorSeed) @@ -407,7 +415,7 @@ func rawSign(sk SecretKey, pk []byte, header []byte, messages [][]byte) (Signatu // Procedure: // 1. (Q_1, H_1, ..., H_L) = create_generators(L+1, PK) - generators := createGenerators(L+1, pk) + generators := createGenerators(L + 1) // 2. domain = calculate_domain(PK, Q_1, (H_1, ..., H_L), header) domain := calculateDomain(pk, generators[0], generators[1:], header) @@ -480,7 +488,7 @@ func rawVerify(pk []byte, signature Signature, header []byte, messages [][]byte) // Procedure: // 1. (Q_1, H_1, ..., H_L) = create_generators(L+1, PK) - generators := createGenerators(L+1, pk) + generators := createGenerators(L + 1) // 2. domain = calculate_domain(PK, Q_1, (H_1, ..., H_L), header) domain := calculateDomain(pk, generators[0], generators[1:], header) @@ -500,7 +508,10 @@ func rawVerify(pk []byte, signature Signature, header []byte, messages [][]byte) // 4. if e(A, W + BP2 * e) * e(B, -BP2) != Identity_GT, return INVALID W := &pairing.G2{} - W.SetBytes(pk) + err := W.SetBytes(pk) + if err != nil { + return fmt.Errorf("bbs: invalid signature") + } lg2 := pairing.G2Generator() lg2.ScalarMult(signature.e, lg2) lg2.Add(lg2, W) @@ -543,30 +554,6 @@ func calculateRandomScalars(count int) ([]*pairing.Scalar, error) { return scalars, nil } -// Mocked random scalars -// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bbs-signatures-03#name-mocked-random-scalars -func calculateFixedScalars(count int) ([]*pairing.Scalar, error) { - // 1. out_len = expand_len * count - expandLength := uint(256) - outLen := uint(int(expandLength) * count) - seed := []byte{0x00} - - // 2. v = expand_message(SEED, dst, out_len) - dst := ciphersuiteString("MOCK_RANDOM_SCALARS_DST_") - exp := expander.NewExpanderMD(crypto.SHA256, dst) - - uniformBytes := exp.Expand(seed, outLen) - scalars := make([]*pairing.Scalar, count) - for i := 0; i < count; i++ { - start := i * int(expandLength) - end := (i + 1) * int(expandLength) - scalars[i] = &pairing.Scalar{} - scalars[i].SetBytes(uniformBytes[start:end]) - } - - return scalars, nil -} - // XXX(caw): refactor this implementation func difference(x []int, count int) []int { if len(x) > count { @@ -620,7 +607,7 @@ func rawProofGen(pk []byte, signature Signature, header []byte, ph []byte, messa // 1. (Q_1, MsgGenerators) = create_generators(L+1, PK) // XXX(Caw): inconsistent notation (Q1, MsgGEnerators) vs (Q1, H1, .... HL) // 2. (H_1, ..., H_L) = MsgGenerators - generators := createGenerators(L+1, pk) + generators := createGenerators(L + 1) msgGenerators := generators[1:] // 3. (H_j1, ..., H_jU) = (MsgGenerators[j1], ..., MsgGenerators[jU]) @@ -755,7 +742,7 @@ func rawProofVerify(pk []byte, proof Proof, header []byte, ph []byte, disclosedM disclosedMsgs := messagesToScalars(disclosedMessages) // 1. (Q_1, MsgGenerators) = create_generators(L+1, PK) - generators := createGenerators(L+1, pk) + generators := createGenerators(L + 1) Q1 := generators[0] // 2. (H_1, ..., H_L) = MsgGenerators @@ -825,7 +812,10 @@ func rawProofVerify(pk []byte, proof Proof, header []byte, ph []byte, disclosedM // 11. if e(Abar, W) * e(Bbar, -BP2) != Identity_GT, return INVALID W := &pairing.G2{} - W.SetBytes(pk) + err = W.SetBytes(pk) + if err != nil { + return fmt.Errorf("bbs: invalid proof") + } l := pairing.Pair(proof.Abar, W) // e(Abar, W) rg2 := pairing.G2Generator() @@ -857,12 +847,12 @@ func ProofVerify(pk, proof, header, ph []byte, disclosedMessages [][]byte, discl func messagesToScalars(messages [][]byte) []*pairing.Scalar { scalars := make([]*pairing.Scalar, len(messages)) for i, msg := range messages { - scalars[i] = mapToScalar(msg, i) + scalars[i] = mapToScalar(msg) } return scalars } -func mapToScalar(msg []byte, index int) *pairing.Scalar { +func mapToScalar(msg []byte) *pairing.Scalar { // dst = ciphersuite_id || "MAP_MSG_TO_SCALAR_AS_HASH_", where ciphersuite_id is defined by the ciphersuite. // XXX(caw): should MAP_MSG_TO_SCALAR_AS_HASH_ be MAP_TO_SCALAR_ID?, e.g., dst = ciphersuite_id || MAP_TO_SCALAR_ID dst := ciphersuiteString("MAP_MSG_TO_SCALAR_AS_HASH_")