diff --git a/note/note_cosigv1.go b/note/note_cosigv1.go index 4cd3fb0..ee90db8 100644 --- a/note/note_cosigv1.go +++ b/note/note_cosigv1.go @@ -109,30 +109,32 @@ func NewMLDSASignerFromCrypto(name string, signer crypto.Signer) (SubtreeSigner, if _, err := c.Unmarshal(msg); err != nil { return nil, err } - return s.signSubtree(t, c.Origin, 0, c.Size, c.Hash) + sigRaw, err := s.signSubtree(t, c.Origin, 0, c.Size, c.Hash) + if err != nil { + return nil, err + } + // Note cosignatures must include the timestamp as prefix. + r := make([]byte, 0, timestampSize+len(sigRaw)) + r = binary.BigEndian.AppendUint64(r, t) + r = append(r, sigRaw...) + return r, nil } s.signSubtree = func(timestamp uint64, logOrigin string, start, end uint64, root []byte) ([]byte, error) { m, err := formatMLDSACosignatureV1(name, timestamp, logOrigin, start, end, root) if err != nil { return nil, err } - sB, err := signer.Sign(nil, m, nil) + sigRaw, err := signer.Sign(nil, m, nil) if err != nil { return nil, err } - // The signature itself is encoded as timestamp || signature. - sig := make([]byte, 0, timestampSize+mldsa.MLDSA44SignatureSize) - sig = binary.BigEndian.AppendUint64(sig, timestamp) - sig = append(sig, sB...) - return sig, nil + return sigRaw, nil } s.verifier = &subtreeVerifier{ - name: name, - keyHash: s.hash, - verifyNote: func(msg, sig []byte) bool { return verifyMLDSACosigV1(pubKey, name)(msg, sig) }, - verifySubtree: func(logOrigin string, start, end uint64, hash []byte, sig []byte) bool { - return verifyMLDSACosigV1Subtree(pubKey, name)(logOrigin, start, end, hash, sig) - }, + name: name, + keyHash: s.hash, + verifyNote: verifyMLDSACosigV1(pubKey, name), + verifySubtree: verifyMLDSACosigV1Subtree(pubKey, name), } return s, nil @@ -160,10 +162,8 @@ func NewMLDSAVerifier(vkey string) (SubtreeVerifier, error) { if err != nil { return nil, err } - v.verifyNote = func(msg, sig []byte) bool { return verifyMLDSACosigV1(pubKey, name)(msg, sig) } - v.verifySubtree = func(logOrigin string, start, end uint64, hash []byte, sig []byte) bool { - return verifyMLDSACosigV1Subtree(pubKey, name)(logOrigin, start, end, hash, sig) - } + v.verifyNote = verifyMLDSACosigV1(pubKey, name) + v.verifySubtree = verifyMLDSACosigV1Subtree(pubKey, name) return v, nil } @@ -365,30 +365,35 @@ func verifyEd25519CosigV1(key []byte) func(msg, sig []byte) bool { // verifyMLDSACosigV1 returns a checkpoint-cosignature verifying function // based on the provided key and cosigner name. // -// Checkpoint signatures are simply subtree signatures over the range [0, size), -// so we parse the checkpoint and then use the subtree verifier to verify -// the signature as a subtree signature. +// Checkpoint signatures are simply subtree signatures over the range [0, size). func verifyMLDSACosigV1(pubKey *mldsa.PublicKey, name string) func(msg, sig []byte) bool { - verifySubtree := verifyMLDSACosigV1Subtree(pubKey, name) return func(msg, sig []byte) bool { c := &log.Checkpoint{} if _, err := c.Unmarshal(msg); err != nil { return false } - return verifySubtree(c.Origin, 0, c.Size, c.Hash, sig) + if len(sig) != timestampSize+mldsa.MLDSA44SignatureSize { + return false + } + timestamp := binary.BigEndian.Uint64(sig) + sig = sig[timestampSize:] + m, err := formatMLDSACosignatureV1(name, timestamp, c.Origin, 0, c.Size, c.Hash) + if err != nil { + return false + } + return mldsa.Verify(pubKey, m, sig, nil) == nil } } // verifyMLDSACosigV1Subtree returns a subtree-cosignature verifying function // based on the provided key and cosigner name. +// By definition, the timestamp used to verify is implicitly zero. func verifyMLDSACosigV1Subtree(pubKey *mldsa.PublicKey, name string) func(logOrigin string, start, end uint64, root []byte, sig []byte) bool { return func(logOrigin string, start, end uint64, root []byte, sig []byte) bool { - if len(sig) != timestampSize+mldsa.MLDSA44SignatureSize { + if len(sig) != mldsa.MLDSA44SignatureSize { return false } - t := binary.BigEndian.Uint64(sig) - sig = sig[timestampSize:] - m, err := formatMLDSACosignatureV1(name, t, logOrigin, start, end, root) + m, err := formatMLDSACosignatureV1(name, 0, logOrigin, start, end, root) if err != nil { return false } @@ -426,7 +431,7 @@ var ( errInvalidPayload = errors.New("invalid message payload") ) -func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin string, start, end uint64, hash []byte) ([]byte, error) { +func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin string, start, end uint64, root []byte) ([]byte, error) { // SPEC: If start is not zero, timestamp MUST be zero. if start > 0 && timestamp > 0 { return nil, errInvalidTimestamp @@ -441,7 +446,7 @@ func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin s if lo, lc := len(logOrigin), len(cosignerName); lo == 0 || lo > 255 || lc == 0 || lc > 255 { return nil, errSignerID } - if len(hash) != sha256.Size { + if len(root) != sha256.Size { return nil, errInvalidPayload } @@ -454,8 +459,8 @@ func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin s // opaque log_origin<1..2^8-1>; // uint64 start; // uint64 end; - // uint8 hash[32]; - // } cosigned_message; + // uint8 root_hash[32]; + // } CosignedSubtree; // // See https://c2sp.org/tlog-cosignature for more details. @@ -469,7 +474,7 @@ func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin s r.AddBytes([]byte(logOrigin)) r.AddUint64(start) r.AddUint64(end) - r.AddBytes(hash) + r.AddBytes(root) return r.Bytes() } @@ -513,7 +518,10 @@ var ( // provide access to a similarly capable verifier. type SubtreeSigner interface { note.Signer - // SignSubtree returns a note-style signature line over the subtree described by the provided arguments. + // SignSubtree returns a signature over the subtree described by the provided arguments. + // The returned signature is a []byte containing the signature value as outputted by the cosignature algorithm. + // Unlike a checkpoint cosignature, there is no timestamp, and the signature is not formatted + // as a note signature line. SignSubtree(logOrigin string, start, end uint64, root []byte) ([]byte, error) // Verifier returns a SubtreeVerifier instance which is able to verify signatures created by this signer instance. Verifier() SubtreeVerifier @@ -532,28 +540,22 @@ func (s *subtreeSigner) Name() string { return s.name } func (s *subtreeSigner) KeyHash() uint32 { return s.hash } func (s *subtreeSigner) Sign(msg []byte) ([]byte, error) { return s.signNote(msg) } func (s *subtreeSigner) SignSubtree(logOrigin string, start, end uint64, root []byte) ([]byte, error) { - stSig, err := s.signSubtree(0, logOrigin, start, end, root) + sig, err := s.signSubtree(0, logOrigin, start, end, root) if err != nil { return nil, err } - // signSubtree gives us back timestamp||signature, so we need to prepend keyhash - // and format into a note signature. - sig := make([]byte, 0, keyHashSize+len(stSig)) - sig = binary.BigEndian.AppendUint32(sig, s.KeyHash()) - sig = append(sig, stSig...) - - res := fmt.Appendf(nil, "— %s %s\n", s.name, base64.StdEncoding.EncodeToString(sig)) - return res, nil + return sig, nil } + func (s *subtreeSigner) Verifier() SubtreeVerifier { return s.verifier } // SubtreeVerifier is a verifier that supports the verification of subtree signatures. type SubtreeVerifier interface { note.Verifier - // VerifySubtree verifies note-formatted signatures over the provided subtree arguments. - // Note that sig MUST be a valid note-formatted signature line, starting with "— " and ending - // with a newline. - VerifySubtree(logOrigin string, start, end uint64, hash []byte, sig []byte) bool + // VerifySubtree verifies signatures over the provided subtree arguments. + // Expects that sig is a raw signature, as produced by SignSubtree, with no leading + // timestamp. + VerifySubtree(logOrigin string, start, end uint64, root []byte, sig []byte) bool } // subtreeVerifier implements the note.Verifier interface to facilitate cosigning operations @@ -563,44 +565,14 @@ type subtreeVerifier struct { name string keyHash uint32 verifyNote func([]byte, []byte) bool - verifySubtree func(logOrigin string, start, end uint64, hash []byte, sig []byte) bool + verifySubtree func(logOrigin string, start, end uint64, root []byte, sig []byte) bool } func (v *subtreeVerifier) Name() string { return v.name } func (v *subtreeVerifier) KeyHash() uint32 { return v.keyHash } func (v *subtreeVerifier) Verify(msg, sig []byte) bool { return v.verifyNote(msg, sig) } -func (v *subtreeVerifier) VerifySubtree(logOrigin string, start, end uint64, hash []byte, sig []byte) bool { - sigLine, ok := strings.CutPrefix(string(sig), "— ") - if !ok { - return false - } - sigLine, ok = strings.CutSuffix(sigLine, "\n") - if !ok { - return false - } - name, sigB64, ok := strings.Cut(sigLine, " ") - if !ok { - return false - } - sigRaw, err := base64.StdEncoding.DecodeString(sigB64) - if err != nil || len(sigRaw) != keyHashSize+timestampSize+mldsa.MLDSA44SignatureSize { - return false - } - if name != v.name || binary.BigEndian.Uint32(sigRaw[:keyHashSize]) != v.keyHash { - return false - } - sigRaw = sigRaw[keyHashSize:] - t := binary.BigEndian.Uint64(sigRaw[:timestampSize]) - // We would expect t == 0 here, IFF the signature was made by our SignSubtree implementation, however: - // SPEC: Semantically, a v1 subtree cosignature is a statement that the subtree with the specified root hash is consistent - // with all other historical views observed by the cosigner of the log identified by the origin line. - // If the timestamp is not zero, it is also a statement that, as of the specified time, this is the largest consistent - // tree the cosigner has observed for the log. - // So we'll handle that here so as to not fail verification of technically correct signatures. - if start != 0 && t != 0 { - return false - } - return v.verifySubtree(logOrigin, start, end, hash, sigRaw) +func (v *subtreeVerifier) VerifySubtree(logOrigin string, start, end uint64, root []byte, sig []byte) bool { + return v.verifySubtree(logOrigin, start, end, root, sig) } // isValidName reports whether name is valid. diff --git a/note/note_cosigv1_test.go b/note/note_cosigv1_test.go index f9c7c63..9dbfa29 100644 --- a/note/note_cosigv1_test.go +++ b/note/note_cosigv1_test.go @@ -357,31 +357,229 @@ func TestSubtreeRoundtrip(t *testing.T) { } origin := "test-log" - var start uint64 = 0 - var end uint64 = 10 root := make([]byte, 32) if _, err := rand.Read(root); err != nil { t.Fatal(err) } - sig, err := signer.SignSubtree(origin, start, end, root) - if err != nil { - t.Fatal(err) - } + for _, test := range []struct { + desc string + start, end uint64 + wantErr bool + }{ + { + desc: "from zero", + start: 0, + end: 10, + }, + { + desc: "non-zero power-of-two size", + start: 8, + end: 16, + }, + { + desc: "non-zero partial subtree", + start: 8, + end: 13, + }, + { + desc: "empty subtree", + start: 10, + end: 10, + }, + { + desc: "invalid: start > end", + start: 10, + end: 8, + wantErr: true, + }, + { + desc: "invalid: misaligned subtree", + start: 2, + end: 6, + wantErr: true, + }, + } { + t.Run(test.desc, func(t *testing.T) { + sig, err := signer.SignSubtree(origin, test.start, test.end, root) + if gotErr := err != nil; gotErr != test.wantErr { + t.Fatalf("SignSubtree(%d, %d): got err %v, wantErr %v", test.start, test.end, err, test.wantErr) + } + if test.wantErr { + return + } - if !verifier.VerifySubtree(origin, start, end, root, sig) { - t.Fatalf("Failed to verify valid subtree signature %q", sig) - } + if !verifier.VerifySubtree(origin, test.start, test.end, root, sig) { + t.Errorf("Failed to verify valid subtree signature %x", sig) + } + + // Test failure cases + wrongRoot := make([]byte, 32) + wrongRoot[0] = root[0] ^ 1 + if verifier.VerifySubtree(origin, test.start, test.end, wrongRoot, sig) { + t.Error("VerifySubtree succeeded with wrong root") + } - // Test failure cases - wrongRoot := make([]byte, 32) - wrongRoot[0] = 1 - if verifier.VerifySubtree(origin, start, end, wrongRoot, sig) { - t.Error("VerifySubtree succeeded with wrong root") + if verifier.VerifySubtree("wrong origin", test.start, test.end, root, sig) { + t.Error("VerifySubtree succeeded with wrong origin") + } + + if verifier.VerifySubtree(origin, test.start+1, test.end, root, sig) { + t.Error("VerifySubtree succeeded with wrong start") + } + + if verifier.VerifySubtree(origin, test.start, test.end+1, root, sig) { + t.Error("VerifySubtree succeeded with wrong end") + } + + corruptedSig := append([]byte(nil), sig...) + corruptedSig[0] ^= 1 + if verifier.VerifySubtree(origin, test.start, test.end, root, corruptedSig) { + t.Error("VerifySubtree succeeded with corrupted signature") + } + }) } +} - if verifier.VerifySubtree("wrong origin", start, end, root, sig) { - t.Error("VerifySubtree succeeded with wrong origin") +func TestIsSubtreeValid(t *testing.T) { + // Includes test vectors from draft-ietf-plants-merkle-tree-certs-07 Appendix C.2.1. + for _, test := range []struct { + desc string + start, end uint64 + want bool + }{ + { + desc: "empty at 0", + start: 0, + end: 0, + want: true, + }, + { + desc: "empty at non-zero", + start: 42, + end: 42, + want: true, + }, + { + desc: "valid [4, 8)", + start: 4, + end: 8, + want: true, + }, + { + desc: "valid [8, 13)", + start: 8, + end: 13, + want: true, + }, + { + desc: "valid [0, 2^47 + 1)", + start: 0, + end: (1 << 47) + 1, + want: true, + }, + { + desc: "valid [0, 2^48 - 1)", + start: 0, + end: (1 << 48) - 1, + want: true, + }, + { + desc: "valid [0, 2^62 + 1)", + start: 0, + end: (1 << 62) + 1, + want: true, + }, + { + desc: "valid [0, 2^63 - 1)", + start: 0, + end: (1 << 63) - 1, + want: true, + }, + { + desc: "valid [0, 2^63 + 1)", + start: 0, + end: (1 << 63) + 1, + want: true, + }, + { + desc: "valid [0, 2^64 - 1)", + start: 0, + end: ^uint64(0), + want: true, + }, + { + desc: "valid [2^63, 2^64 - 1)", + start: 1 << 63, + end: ^uint64(0), + want: true, + }, + { + desc: "invalid: start > end", + start: 10, + end: 8, + want: false, + }, + { + desc: "invalid: misaligned [2, 6)", + start: 2, + end: 6, + want: false, + }, + { + desc: "invalid: misaligned [4, 9)", + start: 4, + end: 9, + want: false, + }, + { + desc: "invalid: [2^46, 2^47 + 1)", + start: 1 << 46, + end: (1 << 47) + 1, + want: false, + }, + { + desc: "invalid: [2^46, 2^48 - 1)", + start: 1 << 46, + end: (1 << 48) - 1, + want: false, + }, + { + desc: "invalid: [2^61, 2^62 + 1)", + start: 1 << 61, + end: (1 << 62) + 1, + want: false, + }, + { + desc: "invalid: [2^61, 2^63 - 1)", + start: 1 << 61, + end: (1 << 63) - 1, + want: false, + }, + { + desc: "invalid: [2^62, 2^63 + 1)", + start: 1 << 62, + end: (1 << 63) + 1, + want: false, + }, + { + desc: "invalid: [2^62, 2^64 - 1)", + start: 1 << 62, + end: ^uint64(0), + want: false, + }, + { + desc: "invalid: size == 2^63 with non-zero start", + start: 1, + end: (1 << 63) + 1, + want: false, + }, + } { + t.Run(test.desc, func(t *testing.T) { + if got := isSubtreeValid(test.start, test.end); got != test.want { + t.Errorf("isSubtreeValid(%d, %d) = %v, want %v", test.start, test.end, got, test.want) + } + }) } } @@ -503,22 +701,55 @@ func TestMLDSASignerFromCrypto(t *testing.T) { func TestMLDSAVerifyTorchwood(t *testing.T) { // Key & Signature generated by filippo.io/torchwood - vkey := "witness.example/w1+7118e8f9+BhQzSuGTb/4Lcu4MREKut7NjCc8jQ0fjuNpEmJwKF9HV5lt3yF7H5jy+LcLOxjh/n4Gud7NOdRP3KpCH055x6Ntr4n6pZlJ+kmvZTJMgs5ygaJVf6q9zdhykYIPQz4fp6qm0fDXlVBqLVti/3zS0OiY5Kqbi0m55WZ9pnUnsiZT6cckK9FoWfXb1UHTsTgz+Uk+kQr4JHdJ/c5t9V6C0WE8JZMiVT0ljBbfP6VACzuEJmc+zbF8Cbd8Mc31PkTR3YWFok3m4YagXfDlzegsE9jg0qWyTWDDkg+rcrcyCMpPam8LsWxQZXZ2O3w9aDbwKB70URNreL/B6jNl95TXJjtEwmspLPxZwwPI8dE7lJbe9N2X0jQQbpcRvm46iLbZO+LdjblTMHfKgbCqgWej/n3QexrovbetYvrNR2qa7BMJk06PDDy9G8EvtNO6xhVp0DG8z4VkIx+Og9eqk3USAg4bKmN1ifuEeMcnlxvvPwnXjMp6kmmhDEsu1Qu4SgEFOG2PcUKdoaXlIDNADmJQHqeiA+JYhjvyTHDM609qpEkHsRYLbxGidKZtaZptOLEL7sLxIzVQMv07xLw2q9qRFxx3yM8JbhJGg5rP28Xhl+3KcGNXy6Ydxuv3MXYCohjwSqAjOdoxs/FzV10HokFOY60vDQLtMUhby7Ob5VviG6+K5tu6hZogYfme3bzZDLO7VMyHMLKKTjfwJS9p1cagSsbqg5MJucfCfXJAw2vWb8n6eUA6qroBLgY9wjkBg06JAoBv85B7caOBjTitplbXk3pfQAzEE9yKEfSDjEfeVOSrBHK3zJQzw0q8RHSqYs+J7AnFgHxZ5IP5TUWkI/jgg3mdxpbaJ/YJ56uNCgFopQZihOCRVc/RFT53Zb1DXIRgqoP9VTIV3l03VPB2NrH49bEAzsCbNiL4NwLchYxoBZkG4Qp/GpTrjCDoDGtR36JipKbmMforXsz5Oh9Yul8s9p1apJXeSCuGsu5OgRBMXjvUoY9p/GFXHEPd6SPEAtDJfBwC5aWDgl4YC5cxKVzs/+xOvzyXmUg7pxtcJ4vIlWmBrmLb4PGVmPurJwx6BcOp7JRcyHCpycAfGMQ7xsBwafuiVCBQHGJhuDKETJa3MAa4H1uTB7GCiKuT8OohZ2LE/395+sftoy/yrX0H6HdCp+QEQ9ZOx0hbVLBOoAY6qVL1bgEBy/YrHgkCxh2lEWdw+9zb6yLWZI6ONX4mBcj3a5dDYfkvm5gAU4MXGAEWnSNeN9+MkylMQjUM5G2Cn7CTrA7IZ23EZ0bGrRMC7PERKbSj2pITgWqxJWH5l2IyK16cviUaXwGaZg8T8t2p77HtccGUodm6rWS0zBhzEyFajBzSi36b9aa3uDeFjsoEonNUevKg/67jcq5Ua0NWqJ80c5q7glj4wWOMJiDHAFh56UzTPGh1zvndQI4ZxuGHRRgOrTiJ4w3llb9EbsEcTlvFOZzjXrZYfMKTdapHV+5Ykok0+avKNnaOPq9pvtvNDx524IGtQCH9i4RjfefsPsxnloXtqYvoV6O8dCiQHn4jBOECYdqA2ZjYVQ/oPNBt5n5TfNJgLBFokcAKVyrTkWrDXi1OHmw6vEtQJLSv3oqchEYIGXWaEi5/OCtruWfALpEl0rifAMgVhhJp2gL+GZU5c3EGOEwOoQQMKDRdYayEWovpu1KIeOMXaJcDle6hCZEseFxXFDg40MeCdXFyv+Ib55h5uJJDxXdiRzQxdKX+deoY=" + // https://go.dev/play/p/tT09aD1mdMl + vkey := "witness.example/w1+a0d0cd91+Bhh6t5zW2IMElD4GJw65/1+GnMosGFhPFst8+uCD+C9/lSovRbjzbFPV7YP9rox2T9pHbsqNmvR3GTnwSFLUZIatFUlXBt0CGLU1J75PMs7G1KNj5yL4R8vvInpzaDvlBUoCLjzegUOh7KImnowtDnJRX38puLf1mStewbP61oBYZmIlWhXproauXnBl0VGbqbd8qeLQ96ENJBhFATSuxbQoJZ3dhGwkHnEQUlqHdEPgYXKwuMWfzKz4X+54eDV4c+IOFcU3/Lmis2aqAG0LoAM4X+eE569R0YVmjXdybbqrCx6dc8gwM9DZp7+gUBwWtI6ccaXJoN6kmdPBC6qUv/PHlCaFCNq170DqAbFJauGRUOBKjGyP9mSQaF6yoNrkdEOsSh039J1uaqDD7X+g5KxFWwM+R1X2HibnEW6okJaIM+MaPsAnZeQLPYbaup/2emXDsHj58+b/5S03DRszJJStFlNxHnMTe+aNyj7vbU8/GD5B3T4es6HsvNbF5+w0PRmZ/1Kkkad4+D6aFj7pMU8eVCpMZtdU8zlYcHOyNZssna8mTL11jfq9NpD5wSo+CYAiDZ/hrW9QjUYpqwzUVTr9gN14mBXfax1g9iTxRAId1ZaHBoYqyitNpE2/797iJVEshYXeh49wGPsPUp+GQYUvYVGr0CmtEYEBoXXDYechA+G1dSyKGgeU5R5ufOkLnumvHW060RKxst3eXKMi1uNyEojsTUhHclL44a6irz95MAWE4k7d9E3Tu8S6mct5WVN4TzyFKZ7PPB+mIMhsbg6n5lI62D3gMKGHTJGbGz17iNZwj0FVp5trL4OGiF6qeK2FLXesOlWYOfOFXvfuLZXvNwmry3aSi/ST+65qXHmAGLQ0ip06A8yEUt3zGZ2VI8whagfAXj2CxcFcPVgg9KI+0aJnbldm6KYcaLoI+fs1iyGQ+zgXrejm/hCyGik+JFVfZGlH+hirnNNglxRsJzIYsFT73ss3+29SnAduHZOYFqLs1aNsDytZgEhC37EL8vqIO7jY1MR8ihldBljWiVV+AQiWpyKvseP5SFP82pcfkGi6DbAAQmifLjGGcm9pCqDIUWrXIyiwt/WbtTgzEuQhdtuYEFeK59BSdpXW9UxUAYG8AjlYL+ylVUjbtz9CYGHtaCoi3NxkaRaUZj2Mtx2WQSyDI2uUTUwOpiqVwJEAE3lpOBqHV58hEosDfAxR1A0bw7px4LFqsfuFcapsP1015sb2IIQn8zx8lfIVtTon2jYMyANqACceBA43Vu9+o7ij+wwEVedRsO5d6/F6KZjL+weSiaIzGRQUazSvkMGZr+3rgaol3wgHkBY/AvJRPqxEZA14+e+HVl7rOTR96o20g72AoDnTj+V5LpggrDEd2eJeYR+1SjphxPKE+Y4U4NJ/Zv68Cji9lmIXAo9ohBGxcjkPdw2Ha2HTE0WT3HwROtbBeFfEvKaAkke9VS5fGmPqkLXzuBmZ4OsRM5zwhW0mcZUjWp4anpuSyngVddL3NyTUg2UBoU3sQVAKr/VtboY0Fn6LuSi+jS6IXUhNkTfHEkZXnWN1oYnQs46/iWbJk0NxjPaCIYceU7DZTLpCuPOVRgm31FcF0AZT/T40gbgzAu7isTXhlOtswI0dpT9k2FidQPsrZh9QZr1lZjcyzqt0M2PeYcctUYY/Y75T5ZNBxxmgQ/EDwu6Y1RUAk9xa7ZrIeJ2ZHXGgwIT7yDQoj8c0nlb/k1qqMbqQey/63s4=" - sig := []byte("— witness.example/w1 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\n") + cp := "example.com/log\n15\nE7ERCtFloiK1fdb+B6/dLG0pM1nmIR0ETKBSauCjtVw=\n\n— witness.example/w1 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\n" - root, _ := base64.StdEncoding.DecodeString("E7ERCtFloiK1fdb+B6/dLG0pM1nmIR0ETKBSauCjtVw=") + v, err := NewMLDSAVerifier(vkey) + if err != nil { + t.Fatalf("Invalid vkey: %v", err) + } + + if _, err := note.Open([]byte(cp), note.VerifierList(v)); err != nil { + t.Fatalf("Failed to verify valid note: %v", err) + } +} + +func TestMLDSAVerifySubtreeTorchwood(t *testing.T) { + // Key & Signature generated by filippo.io/torchwood + // https://go.dev/play/p/tT09aD1mdMl + vkey := "witness.example/w1+a0d0cd91+Bhh6t5zW2IMElD4GJw65/1+GnMosGFhPFst8+uCD+C9/lSovRbjzbFPV7YP9rox2T9pHbsqNmvR3GTnwSFLUZIatFUlXBt0CGLU1J75PMs7G1KNj5yL4R8vvInpzaDvlBUoCLjzegUOh7KImnowtDnJRX38puLf1mStewbP61oBYZmIlWhXproauXnBl0VGbqbd8qeLQ96ENJBhFATSuxbQoJZ3dhGwkHnEQUlqHdEPgYXKwuMWfzKz4X+54eDV4c+IOFcU3/Lmis2aqAG0LoAM4X+eE569R0YVmjXdybbqrCx6dc8gwM9DZp7+gUBwWtI6ccaXJoN6kmdPBC6qUv/PHlCaFCNq170DqAbFJauGRUOBKjGyP9mSQaF6yoNrkdEOsSh039J1uaqDD7X+g5KxFWwM+R1X2HibnEW6okJaIM+MaPsAnZeQLPYbaup/2emXDsHj58+b/5S03DRszJJStFlNxHnMTe+aNyj7vbU8/GD5B3T4es6HsvNbF5+w0PRmZ/1Kkkad4+D6aFj7pMU8eVCpMZtdU8zlYcHOyNZssna8mTL11jfq9NpD5wSo+CYAiDZ/hrW9QjUYpqwzUVTr9gN14mBXfax1g9iTxRAId1ZaHBoYqyitNpE2/797iJVEshYXeh49wGPsPUp+GQYUvYVGr0CmtEYEBoXXDYechA+G1dSyKGgeU5R5ufOkLnumvHW060RKxst3eXKMi1uNyEojsTUhHclL44a6irz95MAWE4k7d9E3Tu8S6mct5WVN4TzyFKZ7PPB+mIMhsbg6n5lI62D3gMKGHTJGbGz17iNZwj0FVp5trL4OGiF6qeK2FLXesOlWYOfOFXvfuLZXvNwmry3aSi/ST+65qXHmAGLQ0ip06A8yEUt3zGZ2VI8whagfAXj2CxcFcPVgg9KI+0aJnbldm6KYcaLoI+fs1iyGQ+zgXrejm/hCyGik+JFVfZGlH+hirnNNglxRsJzIYsFT73ss3+29SnAduHZOYFqLs1aNsDytZgEhC37EL8vqIO7jY1MR8ihldBljWiVV+AQiWpyKvseP5SFP82pcfkGi6DbAAQmifLjGGcm9pCqDIUWrXIyiwt/WbtTgzEuQhdtuYEFeK59BSdpXW9UxUAYG8AjlYL+ylVUjbtz9CYGHtaCoi3NxkaRaUZj2Mtx2WQSyDI2uUTUwOpiqVwJEAE3lpOBqHV58hEosDfAxR1A0bw7px4LFqsfuFcapsP1015sb2IIQn8zx8lfIVtTon2jYMyANqACceBA43Vu9+o7ij+wwEVedRsO5d6/F6KZjL+weSiaIzGRQUazSvkMGZr+3rgaol3wgHkBY/AvJRPqxEZA14+e+HVl7rOTR96o20g72AoDnTj+V5LpggrDEd2eJeYR+1SjphxPKE+Y4U4NJ/Zv68Cji9lmIXAo9ohBGxcjkPdw2Ha2HTE0WT3HwROtbBeFfEvKaAkke9VS5fGmPqkLXzuBmZ4OsRM5zwhW0mcZUjWp4anpuSyngVddL3NyTUg2UBoU3sQVAKr/VtboY0Fn6LuSi+jS6IXUhNkTfHEkZXnWN1oYnQs46/iWbJk0NxjPaCIYceU7DZTLpCuPOVRgm31FcF0AZT/T40gbgzAu7isTXhlOtswI0dpT9k2FidQPsrZh9QZr1lZjcyzqt0M2PeYcctUYY/Y75T5ZNBxxmgQ/EDwu6Y1RUAk9xa7ZrIeJ2ZHXGgwIT7yDQoj8c0nlb/k1qqMbqQey/63s4=" - start, end := uint64(8), uint64(13) - origin := "example.com/log" + sig := "— witness.example/w1 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\n" v, err := NewMLDSAVerifier(vkey) if err != nil { t.Fatalf("Invalid vkey: %v", err) } - if !v.VerifySubtree(origin, start, end, root, sig) { - t.Fatal("Failed to verify valid subtree signature") + // TODO(al): At time of writing, torchwood produces note-style subtree signatures, + // so we'll parse out the raw signature ourselves for now. + _, sig, _ = strings.Cut(sig, " ") // Chomp the dash + _, sig, _ = strings.Cut(sig, " ") // Chomp the name + + // Everything else should be base64 encoded. + sigBytes, err := base64.StdEncoding.DecodeString(sig) + if err != nil { + t.Fatalf("Invalid signature: %v", err) + } + // Drop keyhash (4 bytes) and timestamp (8 bytes) + sigBytes = sigBytes[4+8:] + + if len(sigBytes) != mldsa.MLDSA44SignatureSize { + t.Fatalf("Invalid signature length: %d", len(sigBytes)) + } + + o := "example.com/log" + s, e := uint64(8), uint64(16) + h := sha256.Sum256([]byte("test")) + if !v.VerifySubtree(o, s, e, h[:], sigBytes) { + t.Fatalf("Failed to verify valid subtree signature") } }