Skip to content

Support SHA256/192 parameter sets #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 18, 2025
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
163 changes: 131 additions & 32 deletions lms/common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,53 @@ func (w window) Mask() uint8 {
}
}

type lms_type_code uint32
// lmsTypecode represents a typecode for LMS.
// See https://www.iana.org/assignments/leighton-micali-signatures/leighton-micali-signatures.xhtml#leighton-micali-signatures-1
type lmsTypecode uint32

const (
LMS_RESERVED lms_type_code = iota
LMOTS_SHA256_N32_W1
LMOTS_SHA256_N32_W2
LMOTS_SHA256_N32_W4
LMOTS_SHA256_N32_W8
LMS_SHA256_M32_H5
LMS_SHA256_M32_H10
LMS_SHA256_M32_H15
LMS_SHA256_M32_H20
LMS_SHA256_M32_H25
LMS_RESERVED lmsTypecode = 0x00000000
lmsTypecodeFirst = LMS_SHA256_M32_H5
LMS_SHA256_M32_H5 lmsTypecode = 0x00000005
LMS_SHA256_M32_H10 lmsTypecode = 0x00000006
LMS_SHA256_M32_H15 lmsTypecode = 0x00000007
LMS_SHA256_M32_H20 lmsTypecode = 0x00000008
LMS_SHA256_M32_H25 lmsTypecode = 0x00000009
LMS_SHA256_M24_H5 lmsTypecode = 0x0000000A
LMS_SHA256_M24_H10 lmsTypecode = 0x0000000B
LMS_SHA256_M24_H15 lmsTypecode = 0x0000000C
LMS_SHA256_M24_H20 lmsTypecode = 0x0000000D
LMS_SHA256_M24_H25 lmsTypecode = 0x0000000E
lmsTypecodeLast = LMS_SHA256_M24_H25
)

// lmotsTypecode represents a typecode for LM-OTS.
// See https://www.iana.org/assignments/leighton-micali-signatures/leighton-micali-signatures.xhtml#lm-ots-signatures
type lmotsTypecode uint32

const (
LMOTS_RESERVED lmotsTypecode = 0x00000000
lmotsTypecodeFirst = LMOTS_SHA256_N32_W1
LMOTS_SHA256_N32_W1 lmotsTypecode = 0x00000001
LMOTS_SHA256_N32_W2 lmotsTypecode = 0x00000002
LMOTS_SHA256_N32_W4 lmotsTypecode = 0x00000003
LMOTS_SHA256_N32_W8 lmotsTypecode = 0x00000004
LMOTS_SHA256_N24_W1 lmotsTypecode = 0x00000005
LMOTS_SHA256_N24_W2 lmotsTypecode = 0x00000006
LMOTS_SHA256_N24_W4 lmotsTypecode = 0x00000007
LMOTS_SHA256_N24_W8 lmotsTypecode = 0x00000008
lmotsTypecodeLast = LMOTS_SHA256_N24_W8
)

// LmsAlgorithmType represents a specific instance of LMS
type LmsAlgorithmType interface {
LmsType() (lms_type_code, error)
LmsType() (lmsTypecode, error)
LmsParams() (LmsParam, error)
}

// LmsOtsAlgorithmType represents a specific instance of LM-OTS
type LmsOtsAlgorithmType interface {
LmsOtsType() (lms_type_code, error)
LmsOtsType() (lmotsTypecode, error)
Params() (LmsOtsParam, error)
}

Expand Down Expand Up @@ -99,28 +122,28 @@ type LmsOtsParam struct {
SIG_LEN uint64 // total byte length for a valid signature
}

// Returns a lms_type_code, given a uint32 of the same value
func Uint32ToLmsType(x uint32) lms_type_code {
return lms_type_code(x)
// Returns a lmsTypecode, given a uint32 of the same value
func Uint32ToLmsType(x uint32) lmsTypecode {
return lmsTypecode(x)
}

// Returns a uint32 of the same value as the lms_type_code
func (x lms_type_code) ToUint32() uint32 {
// Returns a uint32 of the same value as the lmsTypecode
func (x lmsTypecode) ToUint32() uint32 {
return uint32(x)
}

// Returns a lms_type_code if within a valid range for LMS; otherwise, an error
func (x lms_type_code) LmsType() (lms_type_code, error) {
if x >= LMS_SHA256_M32_H5 && x <= LMS_SHA256_M32_H25 {
// Returns a lmsTypecode if within a valid range for LMS; otherwise, an error
func (x lmsTypecode) LmsType() (lmsTypecode, error) {
if x >= lmsTypecodeFirst && x <= lmsTypecodeLast {
return x, nil
} else {
return x, errors.New("LmsType(): invalid type code")
}
}

// Returns the expected signature length for an LMS type, given an associated LM-OTS type
func (x lms_type_code) LmsSigLength(otstc lms_type_code) (uint64, error) {
if x >= LMS_SHA256_M32_H5 && x <= LMS_SHA256_M32_H25 {
func (x lmsTypecode) LmsSigLength(otstc lmotsTypecode) (uint64, error) {
if x >= lmsTypecodeFirst && x <= lmsTypecodeLast {
params, err := x.LmsParams()
if err != nil {
return 0, err
Expand All @@ -135,18 +158,28 @@ func (x lms_type_code) LmsSigLength(otstc lms_type_code) (uint64, error) {
}
}

// Returns a lms_type_code if within a valid range for LM-OTS; otherwise, an error
func (x lms_type_code) LmsOtsType() (lms_type_code, error) {
if x >= LMOTS_SHA256_N32_W1 && x <= LMOTS_SHA256_N32_W8 {
// Returns a lmotsTypecode, given a uint32 of the same value
func Uint32ToLmotsType(x uint32) lmotsTypecode {
return lmotsTypecode(x)
}

// Returns a uint32 of the same value as the lmotsTypecode
func (x lmotsTypecode) ToUint32() uint32 {
return uint32(x)
}

// Returns a lmotsTypecode if within a valid range for LM-OTS; otherwise, an error
func (x lmotsTypecode) LmsOtsType() (lmotsTypecode, error) {
if x >= lmotsTypecodeFirst && x <= lmotsTypecodeLast {
return x, nil
} else {
return x, errors.New("LmsOtsType(): invalid type code")
}
}

// Returns the expected byte length of a given LM-OTS signature algorithm
func (x lms_type_code) LmsOtsSigLength() (uint64, error) {
if x >= LMOTS_SHA256_N32_W1 && x <= LMOTS_SHA256_N32_W8 {
func (x lmotsTypecode) LmsOtsSigLength() (uint64, error) {
if x >= lmotsTypecodeFirst && x <= lmotsTypecodeLast {
params, err := x.Params()
if err != nil {
return 0, err
Expand All @@ -157,8 +190,8 @@ func (x lms_type_code) LmsOtsSigLength() (uint64, error) {
}
}

// Returns a LmsParam corresponding to the lms_type_code, x
func (x lms_type_code) LmsParams() (LmsParam, error) {
// Returns a LmsParam corresponding to the lmsTypecode, x
func (x lmsTypecode) LmsParams() (LmsParam, error) {
switch x {
case LMS_SHA256_M32_H5:
return LmsParam{
Expand Down Expand Up @@ -190,13 +223,43 @@ func (x lms_type_code) LmsParams() (LmsParam, error) {
M: 32,
H: 25,
}, nil
case LMS_SHA256_M24_H5:
return LmsParam{
Hash: Sha256Hasher{},
M: 24,
H: 5,
}, nil
case LMS_SHA256_M24_H10:
return LmsParam{
Hash: Sha256Hasher{},
M: 24,
H: 10,
}, nil
case LMS_SHA256_M24_H15:
return LmsParam{
Hash: Sha256Hasher{},
M: 24,
H: 15,
}, nil
case LMS_SHA256_M24_H20:
return LmsParam{
Hash: Sha256Hasher{},
M: 24,
H: 20,
}, nil
case LMS_SHA256_M24_H25:
return LmsParam{
Hash: Sha256Hasher{},
M: 24,
H: 25,
}, nil
default:
return LmsParam{}, errors.New("LmsParams(): invalid type code")
}
}

// Returns a LmsOtsParam corresponding to the lms_type_code, x
func (x lms_type_code) Params() (LmsOtsParam, error) {
// Returns a LmsOtsParam corresponding to the lmsTypecode, x
func (x lmotsTypecode) Params() (LmsOtsParam, error) {
switch x {
case LMOTS_SHA256_N32_W1:
return LmsOtsParam{
Expand Down Expand Up @@ -234,6 +297,42 @@ func (x lms_type_code) Params() (LmsOtsParam, error) {
LS: 0,
SIG_LEN: 1124,
}, nil
case LMOTS_SHA256_N24_W1:
return LmsOtsParam{
H: Sha256Hasher{},
N: 24,
W: WINDOW_W1,
P: 200,
LS: 8,
SIG_LEN: 4828,
}, nil
case LMOTS_SHA256_N24_W2:
return LmsOtsParam{
H: Sha256Hasher{},
N: 24,
W: WINDOW_W2,
P: 101,
LS: 6,
SIG_LEN: 2452,
}, nil
case LMOTS_SHA256_N24_W4:
return LmsOtsParam{
H: Sha256Hasher{},
N: 24,
W: WINDOW_W4,
P: 51,
LS: 4,
SIG_LEN: 1252,
}, nil
case LMOTS_SHA256_N24_W8:
return LmsOtsParam{
H: Sha256Hasher{},
N: 24,
W: WINDOW_W8,
P: 26,
LS: 0,
SIG_LEN: 652,
}, nil
default:
return LmsOtsParam{}, errors.New("Params(): invalid type code")
}
Expand Down
15 changes: 15 additions & 0 deletions lms/common/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package common

import (
"encoding/binary"
"hash"
)

// Returns a []byte representing the Winternitz coefficients of x for a given window, w
Expand Down Expand Up @@ -55,3 +56,17 @@ func Expand(msg []byte, mode LmsOtsAlgorithmType) ([]uint8, error) {

return res[:params.P], nil
}

// HashWrite wraps h.Write with a panic.
func HashWrite(h hash.Hash, x []byte) {
_, err := h.Write(x)
if err != nil {
panic("hash.Hash.Write never errors")
}
}

// HashSum wraps Hash.Sum, returning the n leftmost bytes.
// Panics if n is larger than the length of the hash output.
func HashSum(h hash.Hash, n uint64) []byte {
return h.Sum(nil)[:n]
}
32 changes: 12 additions & 20 deletions lms/lms/private.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,9 @@ import (
"github.com/trailofbits/lms-go/lms/ots"

"crypto/rand"
"hash"
"io"
)

func hash_write(h hash.Hash, x []byte) {
_, err := h.Write(x)
if err != nil {
panic("hash.Hash.Write never errors")
}
}

// NewPrivateKey returns a LmsPrivateKey, seeded by a cryptographically secure
// random number generator.
func NewPrivateKey(tc common.LmsAlgorithmType, otstc common.LmsOtsAlgorithmType) (LmsPrivateKey, error) {
Expand Down Expand Up @@ -186,7 +178,7 @@ func LmsPrivateKeyFromBytes(b []byte) (LmsPrivateKey, error) {
return LmsPrivateKey{}, err
}
// The OTS typecode is bytes 4-7 (4 bytes)
otstype, err := common.Uint32ToLmsType(binary.BigEndian.Uint32(b[4:8])).LmsOtsType()
otstype, err := common.Uint32ToLmotsType(binary.BigEndian.Uint32(b[4:8])).LmsOtsType()
if err != nil {
return LmsPrivateKey{}, err
}
Expand Down Expand Up @@ -249,11 +241,11 @@ func GeneratePKTree(tc common.LmsAlgorithmType, otstc common.LmsOtsAlgorithmType
binary.BigEndian.PutUint32(r_be[:], r)

hasher := ots_params.H.New()
hash_write(hasher, id[:])
hash_write(hasher, r_be[:])
hash_write(hasher, common.D_LEAF[:])
hash_write(hasher, ots_pub.Key())
authtree[r-1] = hasher.Sum(nil)
common.HashWrite(hasher, id[:])
common.HashWrite(hasher, r_be[:])
common.HashWrite(hasher, common.D_LEAF[:])
common.HashWrite(hasher, ots_pub.Key())
authtree[r-1] = common.HashSum(hasher, ots_params.N)

j = i
for j%2 == 1 {
Expand All @@ -263,12 +255,12 @@ func GeneratePKTree(tc common.LmsAlgorithmType, otstc common.LmsOtsAlgorithmType

binary.BigEndian.PutUint32(r_be[:], r)

hash_write(hasher, id[:])
hash_write(hasher, r_be[:])
hash_write(hasher, common.D_INTR[:])
hash_write(hasher, authtree[2*r-1])
hash_write(hasher, authtree[2*r])
authtree[r-1] = hasher.Sum(nil)
common.HashWrite(hasher, id[:])
common.HashWrite(hasher, r_be[:])
common.HashWrite(hasher, common.D_INTR[:])
common.HashWrite(hasher, authtree[2*r-1])
common.HashWrite(hasher, authtree[2*r])
authtree[r-1] = common.HashSum(hasher, ots_params.N)
}
}
return authtree, nil
Expand Down
28 changes: 14 additions & 14 deletions lms/lms/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,27 +67,27 @@ func (pub *LmsPublicKey) Verify(msg []byte, sig LmsSignature) bool {
binary.BigEndian.PutUint32(node_num_bytes[:], node_num)

hasher := ots_params.H.New()
hash_write(hasher, pub.id[:])
hash_write(hasher, node_num_bytes[:])
hash_write(hasher, common.D_LEAF[:])
hash_write(hasher, key_candidate.Key())
tmp := hasher.Sum(nil)
common.HashWrite(hasher, pub.id[:])
common.HashWrite(hasher, node_num_bytes[:])
common.HashWrite(hasher, common.D_LEAF[:])
common.HashWrite(hasher, key_candidate.Key())
tmp := common.HashSum(hasher, ots_params.N)

for i := 0; i < height; i++ {
binary.BigEndian.PutUint32(tmp_be[:], node_num>>1)

hasher := ots_params.H.New()
hash_write(hasher, pub.id[:])
hash_write(hasher, tmp_be[:])
hash_write(hasher, common.D_INTR[:])
common.HashWrite(hasher, pub.id[:])
common.HashWrite(hasher, tmp_be[:])
common.HashWrite(hasher, common.D_INTR[:])
if node_num%2 == 1 {
hash_write(hasher, sig.path[i])
hash_write(hasher, tmp)
common.HashWrite(hasher, sig.path[i])
common.HashWrite(hasher, tmp)
} else {
hash_write(hasher, tmp)
hash_write(hasher, sig.path[i])
common.HashWrite(hasher, tmp)
common.HashWrite(hasher, sig.path[i])
}
tmp = hasher.Sum(nil)
tmp = common.HashSum(hasher, ots_params.N)
node_num >>= 1
}
return subtle.ConstantTimeCompare(tmp, pub.k) == 1
Expand Down Expand Up @@ -143,7 +143,7 @@ func LmsPublicKeyFromBytes(b []byte) (LmsPublicKey, error) {
return LmsPublicKey{}, err
}
// The OTS typecode is bytes 4-7 (4 bytes)
otstype, err := common.Uint32ToLmsType(binary.BigEndian.Uint32(b[4:8])).LmsOtsType()
otstype, err := common.Uint32ToLmotsType(binary.BigEndian.Uint32(b[4:8])).LmsOtsType()
if err != nil {
return LmsPublicKey{}, err
}
Expand Down
Loading