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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 45 additions & 17 deletions note/note_cosigv1.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package note

import (
"bytes"
"crypto"
"crypto/ed25519"
"crypto/sha256"
"encoding/base64"
Expand Down Expand Up @@ -58,27 +59,48 @@ func NewMLDSASigner(skey string) (SubtreeSigner, error) {
if priv1 != "PRIVATE" || priv2 != "KEY" || len(hash16) != 8 || err != nil || !isValidName(name) || len(key) == 0 {
return nil, errSignerID
}
hash, err := strconv.ParseUint(hash16, 16, 32)
if err != nil {
return nil, errInvalidHash
}

alg, key := key[0], key[1:]
if alg != algMLDSA44 {
return nil, errSignerID
}
return newMLDSASigner(name, key)
}

// newMLDSASigner returns a signer for MLDSA cosignature v1, with the provided
// name and key bytes in the format: algo || private key.
func newMLDSASigner(name string, keyBytes []byte) (*subtreeSigner, error) {
s := &subtreeSigner{name: name}
if len(keyBytes) != mldsa.PrivateKeySize {
if len(key) != mldsa.PrivateKeySize {
return nil, errSignerID
}
key, err := mldsa.NewPrivateKey(mldsa.MLDSA44(), keyBytes)
priv, err := mldsa.NewPrivateKey(mldsa.MLDSA44(), key)
if err != nil {
return nil, err
}
s, err := NewMLDSASignerFromCrypto(name, priv)
if err != nil {
return nil, err
}
pubKey := key.PublicKey()
if s.KeyHash() != uint32(hash) {
return nil, errInvalidHash
}
return s, nil
}

// NewMLDSASignerFromCrypto returns a subtree signer for MLDSA cosignature v1 which uses an underlying crypto.Signer for cryptographic operations.
func NewMLDSASignerFromCrypto(name string, signer crypto.Signer) (SubtreeSigner, error) {
if !isValidName(name) {
return nil, errSignerID
}
pubKey, ok := signer.Public().(*mldsa.PublicKey)
if !ok {
return nil, errSignerAlg
}
pubKeyBytes := append([]byte{algMLDSA44}, pubKey.Bytes()...)
s.hash = keyHashMLDSA(name, pubKeyBytes)

s := &subtreeSigner{
name: name,
hash: keyHashMLDSA(name, pubKeyBytes),
}

s.signNote = func(msg []byte) ([]byte, error) {
t := uint64(time.Now().Unix())
c := &log.Checkpoint{}
Expand All @@ -92,7 +114,7 @@ func newMLDSASigner(name string, keyBytes []byte) (*subtreeSigner, error) {
if err != nil {
return nil, err
}
sB, err := key.Sign(nil, m, nil)
sB, err := signer.Sign(nil, m, nil)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -192,13 +214,20 @@ func NewSignerForCosignatureV1(skey string) (Signer, error) {
s.verify = verifyEd25519CosigV1(pubkey[1:])

case algMLDSA44:
stSigner, err := newMLDSASigner(name, key)
if len(key) != mldsa.PrivateKeySize {
return nil, errSignerID
}
priv, err := mldsa.NewPrivateKey(mldsa.MLDSA44(), key)
if err != nil {
return nil, err
}
stSigner, err := NewMLDSASignerFromCrypto(name, priv)
if err != nil {
return nil, err
}
s.sign = stSigner.Sign
s.verify = stSigner.verifier.verifyNote
s.hash = stSigner.hash
s.verify = stSigner.Verifier().Verify
s.hash = stSigner.KeyHash()
}

return s, nil
Expand Down Expand Up @@ -373,7 +402,7 @@ func formatEd25519CosignatureV1(t uint64, msg []byte) ([]byte, error) {
if lines := bytes.Split(msg, []byte("\n")); len(lines) < 3 {
return nil, errors.New("cosigned note format invalid")
}
return []byte(fmt.Sprintf("cosignature/v1\ntime %d\n%s", t, msg)), nil
return fmt.Appendf(nil, "cosignature/v1\ntime %d\n%s", t, msg), nil
}

func formatMLDSACosignatureV1(cosignerName string, timestamp uint64, logOrigin string, start, end uint64, hash []byte) ([]byte, error) {
Expand Down Expand Up @@ -417,7 +446,6 @@ var (
errInvalidTimestamp = errors.New("invalid timestamp")
)


// SubtreeSigner is a note.Signer that can additionally produce subtree signatures, and
// provide access to a similarly capable verifier.
type SubtreeSigner interface {
Expand Down
75 changes: 75 additions & 0 deletions note/note_cosigv1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
package note

import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"strings"
"testing"
"time"

"filippo.io/mldsa"
"golang.org/x/mod/sumdb/note"
)

Expand Down Expand Up @@ -427,3 +431,74 @@ func mustGenerateMLDSAKey(t *testing.T, name string) (string, string) {
}
return skey, vkey
}

func TestMLDSASignerFromCrypto(t *testing.T) {
const name = "mldsa-test"

for _, test := range []struct {
name string
signer crypto.Signer
wantErr bool
}{
{
name: "valid MLDSA signer",
signer: mustMLDSASigner(t),
},
{
name: "invalid signer (not MLDSA)",
signer: mustECDSASigner(t),
wantErr: true,
},
} {
t.Run(test.name, func(t *testing.T) {
signer, err := NewMLDSASignerFromCrypto(name, test.signer)
if gotErr := err != nil; gotErr != test.wantErr {
t.Fatalf("NewMLDSASignerFromCrypto: got err %v, wantErr %v", err, test.wantErr)
}
if test.wantErr {
return
}

if signer.Name() != name {
t.Errorf("signer.Name() = %q, want %q", signer.Name(), name)
}

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)
}
timestamp := uint64(time.Now().Unix())

sig, err := signer.SignSubtree(timestamp, origin, start, end, root)
if err != nil {
t.Fatalf("SignSubtree: %v", err)
}

verifier := signer.Verifier()
if !verifier.VerifySubtree(timestamp, origin, start, end, root, sig) {
t.Error("VerifySubtree failed")
}
})
}
}

func mustMLDSASigner(t *testing.T) crypto.Signer {
t.Helper()
mldsaK, err := mldsa.GenerateKey(mldsa.MLDSA44())
if err != nil {
t.Fatal(err)
}
return mldsaK
}

func mustECDSASigner(t *testing.T) crypto.Signer {
t.Helper()
ecdsaK, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
return ecdsaK
}
Loading