From 9f0d175f2c90f932e22e446e26410e79fdb34181 Mon Sep 17 00:00:00 2001 From: Sun Yimin Date: Wed, 21 May 2025 11:10:44 +0800 Subject: [PATCH] slhdsa: SLH-DSA initialize --- slhdsa/adrs.go | 182 +++++++++++++++++++++ slhdsa/dsa.go | 144 +++++++++++++++++ slhdsa/dsa_test.go | 110 +++++++++++++ slhdsa/fors.go | 134 ++++++++++++++++ slhdsa/hash.go | 161 +++++++++++++++++++ slhdsa/hypertree.go | 60 +++++++ slhdsa/key.go | 196 +++++++++++++++++++++++ slhdsa/key_test.go | 139 ++++++++++++++++ slhdsa/parameterset.go | 123 ++++++++++++++ slhdsa/testdata/SLHDSA128FastSHA2_1.json | 8 + slhdsa/testdata/SLHDSA128FastSHA2_2.json | 9 ++ slhdsa/wots.go | 115 +++++++++++++ slhdsa/xmss.go | 67 ++++++++ 13 files changed, 1448 insertions(+) create mode 100644 slhdsa/adrs.go create mode 100644 slhdsa/dsa.go create mode 100644 slhdsa/dsa_test.go create mode 100644 slhdsa/fors.go create mode 100644 slhdsa/hash.go create mode 100644 slhdsa/hypertree.go create mode 100644 slhdsa/key.go create mode 100644 slhdsa/key_test.go create mode 100644 slhdsa/parameterset.go create mode 100644 slhdsa/testdata/SLHDSA128FastSHA2_1.json create mode 100644 slhdsa/testdata/SLHDSA128FastSHA2_2.json create mode 100644 slhdsa/wots.go create mode 100644 slhdsa/xmss.go diff --git a/slhdsa/adrs.go b/slhdsa/adrs.go new file mode 100644 index 0000000..297a630 --- /dev/null +++ b/slhdsa/adrs.go @@ -0,0 +1,182 @@ +package slhdsa + +type addressType byte + +const ( + AddressTypeWOTSHash addressType = iota + AddressTypeWOTSPK + AddressTypeTree + AddressTypeFORSTree + AddressTypeFORSRoots + AddressTypeWOTSPRF + AddressTypeFORSPRF +) + +type adrsOperations interface { + setLayerAddress(l uint32) + setTreeAddress(t uint64) + setTypeAndClear(y addressType) + setKeyPairAddress(i uint32) + setChainAddress(i uint32) + setHashAddress(i uint32) + setTreeHeight(i uint32) + setTreeIndex(i uint32) + getKeyPairAddress() uint32 + getTreeIndex() uint32 + bytes() []byte + clone(source adrsOperations) + copyKeyPairAddress(source adrsOperations) +} + +type adrs [32]byte + +func (a *adrs) setLayerAddress(l uint32) { + a[0] = byte(l >> 24) + a[1] = byte(l >> 16) + a[2] = byte(l >> 8) + a[3] = byte(l) +} + +func (a *adrs) setTreeAddress(t uint64) { + a[4+4] = byte(t >> 56) + a[4+5] = byte(t >> 48) + a[4+6] = byte(t >> 40) + a[4+7] = byte(t >> 32) + a[4+8] = byte(t >> 24) + a[4+9] = byte(t >> 16) + a[4+10] = byte(t >> 8) + a[4+11] = byte(t) +} + +func (a *adrs) setTypeAndClear(y addressType) { + a[19] = byte(y) + clear(a[20:]) +} + +func (a *adrs) setKeyPairAddress(i uint32) { + a[20] = byte(i >> 24) + a[21] = byte(i >> 16) + a[22] = byte(i >> 8) + a[23] = byte(i) +} + +func (a *adrs) setChainAddress(i uint32) { + a[24] = byte(i >> 24) + a[25] = byte(i >> 16) + a[26] = byte(i >> 8) + a[27] = byte(i) +} + +func (a *adrs) setHashAddress(i uint32) { + a[28] = byte(i >> 24) + a[29] = byte(i >> 16) + a[30] = byte(i >> 8) + a[31] = byte(i) +} + +func (a *adrs) setTreeHeight(i uint32) { + a.setChainAddress(i) +} + +func (a *adrs) setTreeIndex(i uint32) { + a.setHashAddress(i) +} + +func (a *adrs) getKeyPairAddress() uint32 { + return uint32(a[20])<<24 | uint32(a[21])<<16 | uint32(a[22])<<8 | uint32(a[23]) +} + +func (a *adrs) getTreeIndex() uint32 { + return uint32(a[28])<<24 | uint32(a[29])<<16 | uint32(a[30])<<8 | uint32(a[31]) +} + +func (a *adrs) bytes() []byte { + return a[:] +} + +func (a *adrs) clone(b adrsOperations) { + copy(a[:], b.bytes()) +} + +func (a *adrs) copyKeyPairAddress(b adrsOperations) { + copy(a[20:24], b.bytes()[20:24]) +} + +func newAdrs() adrsOperations { + return &adrs{} +} + +type adrsc [22]byte + +func (a *adrsc) setLayerAddress(l uint32) { + a[0] = byte(l) +} + +func (a *adrsc) setTreeAddress(t uint64) { + a[1] = byte(t >> 56) + a[2] = byte(t >> 48) + a[3] = byte(t >> 40) + a[4] = byte(t >> 32) + a[5] = byte(t >> 24) + a[6] = byte(t >> 16) + a[7] = byte(t >> 8) + a[8] = byte(t) +} + +func (a *adrsc) setTypeAndClear(y addressType) { + a[9] = byte(y) + clear(a[10:]) +} + +func (a *adrsc) setKeyPairAddress(i uint32) { + a[10] = byte(i >> 24) + a[11] = byte(i >> 16) + a[12] = byte(i >> 8) + a[13] = byte(i) +} + +func (a *adrsc) setChainAddress(i uint32) { + a[14] = byte(i >> 24) + a[15] = byte(i >> 16) + a[16] = byte(i >> 8) + a[17] = byte(i) +} + +func (a *adrsc) setHashAddress(i uint32) { + a[18] = byte(i >> 24) + a[19] = byte(i >> 16) + a[20] = byte(i >> 8) + a[21] = byte(i) +} + +func (a *adrsc) setTreeHeight(i uint32) { + a.setChainAddress(i) +} + +func (a *adrsc) setTreeIndex(i uint32) { + a.setHashAddress(i) +} + +func (a *adrsc) getKeyPairAddress() uint32 { + return uint32(a[10])<<24 | uint32(a[11])<<16 | uint32(a[12])<<8 | uint32(a[13]) +} + +func (a *adrsc) getTreeIndex() uint32 { + return uint32(a[18])<<24 | uint32(a[19])<<16 | uint32(a[20])<<8 | uint32(a[21]) +} + +func (a *adrsc) bytes() []byte { + return a[:] +} + +func (a *adrsc) clone(b adrsOperations) { + copy(a[:], b.bytes()) +} + +func (a *adrsc) copyKeyPairAddress(b adrsOperations) { + copy(a[10:14], b.bytes()[10:14]) +} + +func newAdrsC() adrsOperations { + return &adrsc{} +} diff --git a/slhdsa/dsa.go b/slhdsa/dsa.go new file mode 100644 index 0000000..8665ef4 --- /dev/null +++ b/slhdsa/dsa.go @@ -0,0 +1,144 @@ +// Copyright 2025 Sun Yimin. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build go1.24 + +package slhdsa + +import ( + "errors" +) + +// Sign generates a pure SLH-DSA signature for the given message. +// The signature is deterministic if the addRand parameter is nil. +// If addRand is not nil, it must be of the same length as n. +// +// See FIPS 205 Algorithm 22 slh_sign +func (sk *PrivateKey) Sign(message, context, addRand []byte) ([]byte, error) { + if len(message) == 0 { + return nil, errors.New("slhdsa: empty message") + } + if len(addRand) > 0 && len(addRand) != int(sk.params.n) { + return nil, errors.New("slhdsa: addrnd should be nil (deterministic variant) or of length n") + } + ctxLen := len(context) + if ctxLen > MAX_CONTEXT_LEN { + return nil, errors.New("slhdsa: context too long") + } + + var mPrefix [MAX_CONTEXT_LEN + 2]byte + + mPrefix[1] = byte(ctxLen) + if ctxLen > 0 { + copy(mPrefix[2:], context) + } + return sk.signInternal(mPrefix[:2+ctxLen], message, addRand) +} + +// See FIPS 205 Algorithm 19 slh_sign_internal +func (sk *PrivateKey) signInternal(msgPrefix, message, addRand []byte) ([]byte, error) { + signatureStart := make([]byte, sk.params.sigLen) + adrs := sk.addressCreator() + + // generate randomizer + if len(addRand) == 0 { + // substitute addRand with sk.PublicKey.seed for the deterministic variant + addRand = sk.PublicKey.seed[:sk.params.n] + } + sk.h.prfMsg(sk, addRand, msgPrefix, message, signatureStart) + R := signatureStart[:sk.params.n] + signature := signatureStart[sk.params.n:] + + // compute message digest + var digest [MAX_M]byte + sk.h.hMsg(&sk.PublicKey, R, msgPrefix, message, digest[:]) + // Grab the first mdLen() bytes of digest to use in fors_sign() + mdLen := sk.params.mdLen() + md := digest[:mdLen] + + // Grab remaining bytes from digest to select tree and leaf id's + remaining := digest[mdLen:] + treeIdxLen := sk.params.treeIdxLen() + leafIdxLen := sk.params.leafIdxLen() + treeIdx := toInt(remaining[:treeIdxLen]) & sk.params.treeIdxMask() + remaining = remaining[treeIdxLen:] + leafIdx := uint32(toInt(remaining[:leafIdxLen]) & sk.params.leafIdxMask()) + + adrs.setTreeAddress(treeIdx) + adrs.setTypeAndClear(AddressTypeFORSTree) + adrs.setKeyPairAddress(leafIdx) + // generate the FORS signature and append it to the SLH-DSA signature + sk.forsSign(md, adrs, signature) + + var pkFors [MAX_N]byte + // calculate the FORS public key using the generated FORS signature + signature = sk.forsPkFromSig(md, signature, adrs, pkFors[:]) + // generate ht signature and append to the SLH-DSA signature + sk.htSign(pkFors[:sk.params.n], treeIdx, leafIdx, signature) + + return signatureStart, nil +} + +// Verify verifies a pure SLH-DSA signature for the given message. +// +// See FIPS 205 Algorithm 24 slh_verify +func (pk *PublicKey) Verify(signature, message, context []byte) bool { + if len(message) == 0 { + return false + } + if len(context) > MAX_CONTEXT_LEN { + return false + } + + ctxLen := len(context) + var msgPrefix [MAX_CONTEXT_LEN + 2]byte + msgPrefix[1] = byte(ctxLen) + if ctxLen > 0 { + copy(msgPrefix[2:], context) + } + return pk.verifyInternal(signature, msgPrefix[:2+ctxLen], message) +} + +// See FIPS 205 Algorithm 20 slh_verify_internal +func (pk *PublicKey) verifyInternal(signature []byte, msgPrefix []byte, message []byte) bool { + if len(signature) != pk.params.sigLen { + return false + } + adrs := pk.addressCreator() + R := signature[:pk.params.n] + signature = signature[pk.params.n:] + + // compute message digest + var digest [MAX_M]byte + pk.h.hMsg(pk, R, msgPrefix, message, digest[:]) + // Grab the first mdLen() bytes of digest to use in fors_sign() + mdLen := pk.params.mdLen() + md := digest[:mdLen] + + // Grab remaining bytes from digest to select tree and leaf id's + remaining := digest[mdLen:] + treeIdxLen := pk.params.treeIdxLen() + leafIdxLen := pk.params.leafIdxLen() + treeIdx := toInt(remaining[:treeIdxLen]) & pk.params.treeIdxMask() + remaining = remaining[treeIdxLen:] + leafIdx := uint32(toInt(remaining[:leafIdxLen]) & pk.params.leafIdxMask()) + + adrs.setTreeAddress(treeIdx) + adrs.setTypeAndClear(AddressTypeFORSTree) + adrs.setKeyPairAddress(leafIdx) + + var pkFors [MAX_N]byte + // calculate the FORS public key using the given FORS signature + signature = pk.forsPkFromSig(md, signature, adrs, pkFors[:]) + + return pk.htVerify(pkFors[:pk.params.n], signature, treeIdx, leafIdx) +} + +func toInt(b []byte) uint64 { + var ret uint64 + for i := range b { + ret = ret<<8 + uint64(b[i]) + } + return ret +} diff --git a/slhdsa/dsa_test.go b/slhdsa/dsa_test.go new file mode 100644 index 0000000..81b5c18 --- /dev/null +++ b/slhdsa/dsa_test.go @@ -0,0 +1,110 @@ +// Copyright 2025 Sun Yimin. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build go1.24 + +package slhdsa + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "os" + "path/filepath" + "testing" +) + +type slhtest struct { + ParameterSet string `json:"parameterSet"` + Sk string `json:"sk"` + Pk string `json:"pk"` + AdditionalRandomness string `json:"additionalRandomness,omitempty"` + Message string `json:"message"` + Context string `json:"context,omitempty"` + Signature string `json:"signature"` +} + +func loadData(filename string) (*slhtest, error) { + file := filepath.Join("testdata", filename) + // read json conentent from file and Unmarshal to slhtest + in, err := os.Open(file) + if err != nil { + return nil, err + } + defer in.Close() + var data slhtest + if err := json.NewDecoder(in).Decode(&data); err != nil { + return nil, err + } + return &data, nil +} + +func TestSignFromFiles(t *testing.T) { + // list files in testdata + files, err := os.ReadDir("testdata") + if err != nil { + t.Fatalf("ReadDir failed: %v", err) + } + for _, file := range files { + if file.IsDir() { + continue + } + data, err := loadData(file.Name()) + if err != nil { + t.Fatalf("loadData failed: %v", err) + } + testData(t, file.Name(), data) + } +} + +func testData(t *testing.T, filename string, tc *slhtest) { + t.Helper() + params, ok := GetParameterSet(tc.ParameterSet) + if !ok { + t.Fatalf("%v GetParameterSet(%s)", filename, tc.ParameterSet) + } + skBytes, _ := hex.DecodeString(tc.Sk) + pkBytes, _ := hex.DecodeString(tc.Pk) + addRand, _ := hex.DecodeString(tc.AdditionalRandomness) + message, _ := hex.DecodeString(tc.Message) + context, _ := hex.DecodeString(tc.Context) + sig, _ := hex.DecodeString(tc.Signature) + sigOriginal := sig + privKey, err := NewPrivateKey(skBytes, params) + if err != nil { + t.Fatalf("%v NewPrivateKey(%x) = %v", filename, skBytes, err) + } + sig2, err := privKey.Sign(message, context, addRand) + if err != nil { + t.Fatalf("%v Sign(%x,%x) = %v", filename, message, context, err) + } + // check R + if !bytes.Equal(sig[:params.n], sig2[:params.n]) { + t.Errorf("signature.R = %x, want %x", sig2[:params.n], sig[:params.n]) + } + // check SIGfors + sig2 = sig2[params.n:] + sig = sig[params.n:] + if !bytes.Equal(sig[:privKey.params.n*(privKey.params.a+1)*privKey.params.k], sig2[:privKey.params.n*(privKey.params.a+1)*privKey.params.k]) { + t.Errorf("signature.SIGfors = %x, want %x", sig2[:privKey.params.n*(privKey.params.a+1)*privKey.params.k], sig[:privKey.params.n*(privKey.params.a+1)*privKey.params.k]) + } + // check SIGht + sig2 = sig2[privKey.params.n*(privKey.params.a+1)*privKey.params.k:] + sig = sig[privKey.params.n*(privKey.params.a+1)*privKey.params.k:] + for i := range int(privKey.params.d) { + if !bytes.Equal(sig[:privKey.params.n*(privKey.params.len+privKey.params.hm)], sig2[:privKey.params.n*(privKey.params.len+privKey.params.hm)]) { + t.Errorf("signature.SIGht = %v %x, want %x", i, sig2[:privKey.params.n*(privKey.params.len+privKey.params.hm)], sig[:privKey.params.n*(privKey.params.len+privKey.params.hm)]) + } + sig2 = sig2[privKey.params.n*(privKey.params.len+privKey.params.hm):] + sig = sig[privKey.params.n*(privKey.params.len+privKey.params.hm):] + } + // test verify + pub, err := NewPublicKey(pkBytes, params) + if err != nil { + t.Fatalf("%v NewPublicKey(%x) = %v", filename, pkBytes, err) + } + if !pub.Verify(sigOriginal, message, context) { + t.Errorf("%v Verify() = false, want true", filename) + } +} diff --git a/slhdsa/fors.go b/slhdsa/fors.go new file mode 100644 index 0000000..2955296 --- /dev/null +++ b/slhdsa/fors.go @@ -0,0 +1,134 @@ +// Copyright 2025 Sun Yimin. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build go1.24 + +package slhdsa + +// forsSign generates a FORS signature. +// See FIPS 205 Algorithm 16 fors_sign +func (sk *PrivateKey) forsSign(md []byte, adrs adrsOperations, sigFors []byte) { + var indices [MAX_K]uint32 + // split md into k a-bits values + base2b(md, sk.params.a, indices[:sk.params.k]) + + twoPowerA := uint32(1 << sk.params.a) + var treeIDTimeTwoPowerA uint32 + + for treeID := range sk.params.k { + nodeID := indices[treeID] + sk.forsGenPrivateKey(nodeID+treeIDTimeTwoPowerA, adrs, sigFors) + sigFors = sigFors[sk.params.n:] + + treeOffset := treeIDTimeTwoPowerA + for layer := range sk.params.a { + s := nodeID ^ 1 + sk.forsNode(s+treeOffset, layer, adrs, sigFors) + + nodeID >>= 1 + treeOffset >>= 1 + sigFors = sigFors[sk.params.n:] + } + treeIDTimeTwoPowerA += twoPowerA + } +} + +// forsPkFromSig computes a FORS public key from a FORS signature. +// See FIPS 205 Algorithm 17 fors_pkFromSig +func (pk *PublicKey) forsPkFromSig(md, signature []byte, adrs adrsOperations, out []byte) []byte { + var indices [MAX_K]uint32 + base2b(md, pk.params.a, indices[:pk.params.k]) + + twoPowerA := uint32(1 << pk.params.a) + + var treeIDTimeTwoPowerA uint32 + // TODO: use array to avoid heap allocation? + root := make([]byte, pk.params.n*pk.params.k) + rootPt := root + for treeID := range pk.params.k { + // compute leaf + nodeID := indices[treeID] + treeIdx := nodeID + treeIDTimeTwoPowerA + adrs.setTreeHeight(0) + adrs.setTreeIndex(treeIdx) + pk.h.f(pk, adrs, signature, rootPt) + signature = signature[pk.params.n:] + + // compute root from leaf and AUTH + for layer := range pk.params.a { + adrs.setTreeHeight(layer + 1) + if nodeID&1 == 0 { + treeIdx = treeIdx >> 1 + adrs.setTreeIndex(treeIdx) + pk.h.h(pk, adrs, rootPt, signature, rootPt) + } else { + treeIdx = (treeIdx - 1) >> 1 + adrs.setTreeIndex(treeIdx) + pk.h.h(pk, adrs, signature, rootPt, rootPt) + } + signature = signature[pk.params.n:] + nodeID >>= 1 + } + treeIDTimeTwoPowerA += twoPowerA + rootPt = rootPt[pk.params.n:] + } + // copy address to create a FORS public-key address + forspkADRS := pk.addressCreator() + forspkADRS.clone(adrs) + forspkADRS.setTypeAndClear(AddressTypeFORSRoots) + forspkADRS.copyKeyPairAddress(adrs) + pk.h.t(pk, forspkADRS, root, out) + clear(root) + return signature +} + +// forsNode computes the root of a Merkle subtree of FORS public values. +// See FIPS 205 Algorithm 15 fors_node +func (sk *PrivateKey) forsNode(nodeID, layer uint32, adrs adrsOperations, out []byte) { + if layer == 0 { + sk.forsGenPrivateKey(nodeID, adrs, out) + adrs.setTreeHeight(0) + adrs.setTreeIndex(nodeID) + sk.h.f(&sk.PublicKey, adrs, out, out) + } else { + var lnode, rnode [MAX_N]byte + sk.forsNode(nodeID*2, layer-1, adrs, lnode[:]) + sk.forsNode(nodeID*2+1, layer-1, adrs, rnode[:]) + adrs.setTreeHeight(layer) + adrs.setTreeIndex(nodeID) + sk.h.h(&sk.PublicKey, adrs, lnode[:], rnode[:], out) + } +} + +// forsGenPrivateKey generates a FORS private key value. +// See FIPS 205 Algorithm 14 fors_skGen +func (sk *PrivateKey) forsGenPrivateKey(i uint32, adrs adrsOperations, out []byte) { + skADRS := sk.addressCreator() + skADRS.clone(adrs) + skADRS.setTypeAndClear(AddressTypeFORSPRF) + skADRS.copyKeyPairAddress(adrs) + skADRS.setTreeIndex(i) + sk.h.prf(sk, skADRS, out) +} + +// base2b computes the base-2^b representation of the input byte array. +// See FIPS 205 Algorithm 4 base_2^b +func base2b(in []byte, base uint32, out []uint32) { + var ( + bits uint32 + total uint32 + mask uint32 + idx int + ) + mask = 1<> bits) & mask + } +} diff --git a/slhdsa/hash.go b/slhdsa/hash.go new file mode 100644 index 0000000..ba9d672 --- /dev/null +++ b/slhdsa/hash.go @@ -0,0 +1,161 @@ +// Copyright 2025 Sun Yimin. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build go1.24 + +package slhdsa + +import ( + "crypto/hmac" + "hash" +) + +type hashOperations interface { + f(pk *PublicKey, address adrsOperations, m1, out []byte) + h(pk *PublicKey, address adrsOperations, m1, m2, out []byte) + t(pk *PublicKey, address adrsOperations, ml, out []byte) + hMsg(pk *PublicKey, R, mPrefix, M, out []byte) + prf(sk *PrivateKey, address adrsOperations, out []byte) + prfMsg(sk *PrivateKey, optRand, mPrefix, m, out []byte) +} + +type shakeOperations struct{} + +func (shakeOperations) f(pk *PublicKey, address adrsOperations, m1, out []byte) { + pk.shake.Reset() + pk.shake.Write(pk.seed[:pk.params.n]) + pk.shake.Write(address.bytes()) + pk.shake.Write(m1[:pk.params.n]) + pk.shake.Read(out[:pk.params.n]) +} + +func (shakeOperations) h(pk *PublicKey, address adrsOperations, m1, m2, out []byte) { + pk.shake.Reset() + pk.shake.Write(pk.seed[:pk.params.n]) + pk.shake.Write(address.bytes()) + pk.shake.Write(m1[:pk.params.n]) + pk.shake.Write(m2[:pk.params.n]) + pk.shake.Read(out[:pk.params.n]) +} + +func (shakeOperations) t(pk *PublicKey, address adrsOperations, ml, out []byte) { + pk.shake.Reset() + pk.shake.Write(pk.seed[:pk.params.n]) + pk.shake.Write(address.bytes()) + pk.shake.Write(ml) + pk.shake.Read(out[:pk.params.n]) +} + +func (shakeOperations) hMsg(pk *PublicKey, R, mPrefix, M, out []byte) { + pk.shake.Reset() + pk.shake.Write(R[:pk.params.n]) + pk.shake.Write(pk.seed[:pk.params.n]) + pk.shake.Write(pk.root[:pk.params.n]) + pk.shake.Write(mPrefix) + pk.shake.Write(M) + pk.shake.Read(out[:pk.params.m]) +} + +func (shakeOperations) prf(sk *PrivateKey, address adrsOperations, out []byte) { + sk.shake.Reset() + sk.shake.Write(sk.PublicKey.seed[:sk.params.n]) + sk.shake.Write(address.bytes()) + sk.shake.Write(sk.seed[:sk.params.n]) + sk.shake.Read(out[:sk.params.n]) +} + +func (shakeOperations) prfMsg(sk *PrivateKey, optRand, mPrefix, m, out []byte) { + sk.shake.Reset() + sk.shake.Write(sk.prf[:sk.params.n]) + sk.shake.Write(optRand) + sk.shake.Write(mPrefix) + sk.shake.Write(m) + sk.shake.Read(out[:sk.params.n]) +} + +type sha2Operations struct{} + +func (sha2Operations) f(pk *PublicKey, address adrsOperations, m1, out []byte) { + var zeros [64]byte + pk.md.Reset() + pk.md.Write(pk.seed[:pk.params.n]) + pk.md.Write(zeros[:64-pk.params.n]) + pk.md.Write(address.bytes()) + pk.md.Write(m1[:pk.params.n]) + pk.md.Sum(zeros[:0]) + copy(out, zeros[:pk.params.n]) +} + +func (sha2Operations) h(pk *PublicKey, address adrsOperations, m1, m2, out []byte) { + var zeros [128]byte + pk.mdBig.Reset() + pk.mdBig.Write(pk.seed[:pk.params.n]) + pk.mdBig.Write(zeros[:uint32(pk.mdBig.BlockSize())-pk.params.n]) + pk.mdBig.Write(address.bytes()) + pk.mdBig.Write(m1[:pk.params.n]) + pk.mdBig.Write(m2[:pk.params.n]) + pk.mdBig.Sum(zeros[:0]) + copy(out, zeros[:pk.params.n]) +} + +func (sha2Operations) t(pk *PublicKey, address adrsOperations, ml, out []byte) { + var zeros [128]byte + pk.mdBig.Reset() + pk.mdBig.Write(pk.seed[:pk.params.n]) + pk.mdBig.Write(zeros[:uint32(pk.mdBig.BlockSize())-pk.params.n]) + pk.mdBig.Write(address.bytes()) + pk.mdBig.Write(ml) + pk.mdBig.Sum(zeros[:0]) + copy(out, zeros[:pk.params.n]) +} + +func (sha2Operations) prfMsg(sk *PrivateKey, optRand, mPrefix, m, out []byte) { + var buf [128]byte + mac := hmac.New(sk.mdBigFactory, sk.prf[:sk.params.n]) + mac.Write(optRand) + mac.Write(mPrefix) + mac.Write(m) + mac.Sum(buf[:0]) + copy(out, buf[:sk.params.n]) +} + +func (sha2Operations) prf(sk *PrivateKey, address adrsOperations, out []byte) { + var zeros [128]byte + sk.md.Reset() + sk.md.Write(sk.PublicKey.seed[:sk.params.n]) + sk.md.Write(zeros[:64-sk.params.n]) + sk.md.Write(address.bytes()) + sk.md.Write(sk.seed[:sk.params.n]) + sk.md.Sum(zeros[:0]) + copy(out, zeros[:sk.params.n]) +} + +func (sha2Operations) hMsg(pk *PublicKey, R, mPrefix, M, out []byte) { + var buf [128]byte + pk.mdBig.Reset() + pk.mdBig.Write(R[:pk.params.n]) + pk.mdBig.Write(pk.seed[:pk.params.n]) + pk.mdBig.Write(pk.root[:pk.params.n]) + pk.mdBig.Write(mPrefix) + pk.mdBig.Write(M) + pk.mdBig.Sum(buf[:0]) + mgf1([][]byte{R[:pk.params.n], pk.seed[:pk.params.n], buf[:pk.mdBig.Size()]}, pk.mdBig, out[:pk.params.m]) +} + +func mgf1(seeds [][]byte, h hash.Hash, out []byte) { + var counter uint32 + var buff [128]byte + size := h.Size() + maskLen := len(out) + for i := 0; i < maskLen; i += size { + h.Reset() + for _, seed := range seeds { + h.Write(seed) + } + h.Write([]byte{byte(counter >> 24), byte(counter >> 16), byte(counter >> 8), byte(counter)}) + h.Sum(buff[:0]) + copy(out[i:], buff[:size]) + counter++ + } +} diff --git a/slhdsa/hypertree.go b/slhdsa/hypertree.go new file mode 100644 index 0000000..ce1155c --- /dev/null +++ b/slhdsa/hypertree.go @@ -0,0 +1,60 @@ +// Copyright 2025 Sun Yimin. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build go1.24 + +package slhdsa + +import "crypto/subtle" + +// htSign generates a hypertree signature. +// See FIPS 205 Algorithm 12 ht_sign +func (sk *PrivateKey) htSign(pkFors []byte, treeIdx uint64, leafIdx uint32, signature []byte) { + adrs := sk.addressCreator() + + sigLenPerLayer := (sk.params.hm + sk.params.len) * sk.params.n + mask := sk.params.leafIdxMask() + + var rootBuf [MAX_N]byte + root := rootBuf[:sk.params.n] + copy(root, pkFors) + for j := range sk.params.d { + adrs.setLayerAddress(j) + adrs.setTreeAddress(treeIdx) + sk.xmssSign(root, leafIdx, adrs, signature) + + if j < sk.params.d-1 { + sk.xmssPkFromSig(leafIdx, signature, root, adrs, root) + // hm least significant bits of treeIdx + leafIdx = uint32(treeIdx & mask) + // remove least significant hm bits from treeIdx + treeIdx >>= sk.params.hm + signature = signature[sigLenPerLayer:] + } + } +} + +// htVerify verifies a hypertree signature. +// See FIPS 205 Algorithm 13 ht_verify +func (pk *PublicKey) htVerify(pkFors []byte, signature []byte, treeIdx uint64, leafIdx uint32) bool { + adrs := pk.addressCreator() + + sigLenPerLayer := (pk.params.hm + pk.params.len) * pk.params.n + mask := pk.params.leafIdxMask() + + var rootBuf [MAX_N]byte + root := rootBuf[:pk.params.n] + copy(root, pkFors) + for j := range pk.params.d { + adrs.setLayerAddress(j) + adrs.setTreeAddress(treeIdx) + pk.xmssPkFromSig(leafIdx, signature, root, adrs, root) + // hm least significant bits of treeIdx + leafIdx = uint32(treeIdx & mask) + // remove least significant hm bits from treeIdx + treeIdx >>= pk.params.hm + signature = signature[sigLenPerLayer:] + } + return subtle.ConstantTimeCompare(pk.root[:pk.params.n], root) == 1 +} diff --git a/slhdsa/key.go b/slhdsa/key.go new file mode 100644 index 0000000..0c34d4e --- /dev/null +++ b/slhdsa/key.go @@ -0,0 +1,196 @@ +// Copyright 2025 Sun Yimin. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build go1.24 + +package slhdsa + +import ( + "crypto/sha256" + "crypto/sha3" + "crypto/sha512" + "crypto/subtle" + "errors" + "hash" + "io" + + "github.com/emmansun/gmsm/sm3" +) + +type PublicKey struct { + seed [MAX_N]byte + root [MAX_N]byte + params *params + md hash.Hash + mdBig hash.Hash + mdBigFactory func() hash.Hash + shake *sha3.SHAKE + addressCreator func() adrsOperations + h hashOperations +} + +type PrivateKey struct { + PublicKey + seed [MAX_N]byte + prf [MAX_N]byte +} + +// Bytes returns the byte representation of the PublicKey. +// It combines the seed and root fields of the PublicKey. +func (pk *PublicKey) Bytes() []byte { + var key [2 * MAX_N]byte + copy(key[:], pk.seed[:pk.params.n]) + copy(key[pk.params.n:], pk.root[:pk.params.n]) + return key[:2*pk.params.n] +} + +func (pk *PublicKey) Equal(x any) bool { + xx, ok := x.(*PublicKey) + if !ok { + return false + } + return pk.params == xx.params && subtle.ConstantTimeCompare(pk.seed[:pk.params.n], xx.seed[:pk.params.n]) == 1 && + subtle.ConstantTimeCompare(pk.root[:pk.params.n], xx.root[:pk.params.n]) == 1 +} + +// Bytes serializes the PrivateKey into a byte slice. +func (sk *PrivateKey) Bytes() []byte { + var key [4 * MAX_N]byte + keySlice := key[:] + copy(keySlice, sk.seed[:sk.params.n]) + keySlice = keySlice[sk.params.n:] + copy(keySlice, sk.prf[:sk.params.n]) + keySlice = keySlice[sk.params.n:] + copy(keySlice, sk.PublicKey.seed[:sk.params.n]) + keySlice = keySlice[sk.params.n:] + copy(keySlice, sk.root[:sk.params.n]) + return key[:4*sk.params.n] +} + +// PublicKey returns the public key of the private key. +func (sk *PrivateKey) Public() *PublicKey { + return &sk.PublicKey +} + +func (sk *PrivateKey) Equal(x any) bool { + xx, ok := x.(*PrivateKey) + if !ok { + return false + } + return sk.params == xx.params && subtle.ConstantTimeCompare(sk.seed[:sk.params.n], xx.seed[:sk.params.n]) == 1 && + subtle.ConstantTimeCompare(sk.prf[:sk.params.n], xx.prf[:sk.params.n]) == 1 && + subtle.ConstantTimeCompare(sk.PublicKey.seed[:sk.params.n], xx.PublicKey.seed[:sk.params.n]) == 1 && + subtle.ConstantTimeCompare(sk.root[:sk.params.n], xx.root[:sk.params.n]) == 1 +} + +// GenerateKey generates a new private key based on the provided parameters. +// It initializes the key structure, fills the necessary fields with provided entropy, +// and computes the root node for the XMSS tree. +func GenerateKey(rand io.Reader, params *params) (*PrivateKey, error) { + priv := &PrivateKey{} + if err := initKey(params, &priv.PublicKey); err != nil { + return nil, err + } + if _, err := io.ReadFull(rand, priv.seed[:params.n]); err != nil { + return nil, err + } + if _, err := io.ReadFull(rand, priv.prf[:params.n]); err != nil { + return nil, err + } + if _, err := io.ReadFull(rand, priv.PublicKey.seed[:params.n]); err != nil { + return nil, err + } + adrs := priv.addressCreator() + adrs.setLayerAddress(params.d - 1) + priv.xmssNode(priv.root[:], 0, params.hm, adrs) + return priv, nil +} + +// NewPrivateKey creates a new PrivateKey instance from the provided priv.seed||priv.prf||pub.seed||pub.root and parameters. +// The function validates the length of the input byte slice and initializes the PrivateKey structure, +// including its PublicKey field. It also verifies the integrity of the key by comparing the root hash +// with the expected value. If any validation or initialization step fails, an error is returned. +func NewPrivateKey(bytes []byte, params *params) (*PrivateKey, error) { + if len(bytes) != 4*int(params.n) { + return nil, errors.New("slhdsa: invalid key length") + } + priv := &PrivateKey{} + if err := initKey(params, &priv.PublicKey); err != nil { + return nil, err + } + copy(priv.seed[:], bytes[:params.n]) + copy(priv.prf[:], bytes[params.n:2*params.n]) + copy(priv.PublicKey.seed[:], bytes[2*params.n:3*params.n]) + + adrs := priv.addressCreator() + adrs.setLayerAddress(params.d - 1) + priv.xmssNode(priv.root[:], 0, params.hm, adrs) + if subtle.ConstantTimeCompare(priv.root[:params.n], bytes[3*params.n:]) != 1 { + return nil, errors.New("slhdsa: invalid key") + } + return priv, nil +} + +// NewPublicKey creates a new PublicKey instance from the provided seed||root and parameters. +// Note this method can NOT verify the validity of the public key. +func NewPublicKey(bytes []byte, params *params) (*PublicKey, error) { + if len(bytes) != 2*int(params.n) { + return nil, errors.New("slhdsa: invalid key length") + } + pub := &PublicKey{} + if err := initKey(params, pub); err != nil { + return nil, err + } + copy(pub.seed[:], bytes[:params.n]) + copy(pub.root[:], bytes[params.n:2*params.n]) + return pub, nil +} + +func generateKeyInernal(skSeed, skPRF, pkSeed []byte, params *params) (*PrivateKey, error) { + priv := &PrivateKey{} + if err := initKey(params, &priv.PublicKey); err != nil { + return nil, err + } + if len(skSeed) != int(params.n) || len(skPRF) != int(params.n) || len(pkSeed) != int(params.n) { + return nil, errors.New("slhdsa: invalid seed/prf length") + } + copy(priv.seed[:], skSeed) + copy(priv.prf[:], skPRF) + copy(priv.PublicKey.seed[:], pkSeed) + adrs := priv.addressCreator() + adrs.setLayerAddress(params.d - 1) + priv.xmssNode(priv.root[:], 0, params.hm, adrs) + return priv, nil +} + +func initKey(params *params, key *PublicKey) error { + switch params { + case &SLHDSA128SmallSHA2, &SLHDSA128FastSHA2: + key.md = sha256.New() + key.mdBig = key.md + key.mdBigFactory = sha256.New + key.h = sha2Operations{} + key.addressCreator = newAdrsC + case &SLHDSA128SmallSM3, &SLHDSA128FastSM3: + key.md = sm3.New() + key.mdBig = key.md + key.mdBigFactory = sm3.New + key.h = sha2Operations{} + key.addressCreator = newAdrsC + case &SLHDSA192SmallSHA2, &SLHDSA192FastSHA2, &SLHDSA256SmallSHA2, &SLHDSA256FastSHA2: + key.md = sha256.New() + key.mdBig = sha512.New() + key.mdBigFactory = sha512.New + key.h = sha2Operations{} + key.addressCreator = newAdrsC + case &SLHDSA128SmallSHAKE, &SLHDSA128FastSHAKE, &SLHDSA192SmallSHAKE, &SLHDSA192FastSHAKE, &SLHDSA256SmallSHAKE, &SLHDSA256FastSHAKE: + key.shake = sha3.NewSHAKE256() + key.h = shakeOperations{} + key.addressCreator = newAdrs + default: + return errors.New("slhdsa: unsupported parameters") + } + key.params = params + return nil +} diff --git a/slhdsa/key_test.go b/slhdsa/key_test.go new file mode 100644 index 0000000..7a02e47 --- /dev/null +++ b/slhdsa/key_test.go @@ -0,0 +1,139 @@ +// Copyright 2025 Sun Yimin. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build go1.24 + +package slhdsa + +import ( + "bytes" + "encoding/hex" + "testing" +) + +var keyCases = []struct { + params *params + skSeed string + skPRF string + pkSeed string + pkRoot string +}{ + { + params: &SLHDSA128SmallSHA2, + skSeed: "AC379F047FAAB2004F3AE32350AC9A3D", + skPRF: "829FFF0AA59E956A87F3971C4D58E710", + pkSeed: "0566D240CC519834322EAFBCC73C79F5", + pkRoot: "A4B84F02E8BF0CBD54017B2D3C494B57", + }, + { + params: &SLHDSA128SmallSM3, + skSeed: "AC379F047FAAB2004F3AE32350AC9A3D", + skPRF: "829FFF0AA59E956A87F3971C4D58E710", + pkSeed: "0566D240CC519834322EAFBCC73C79F5", + pkRoot: "5EECDA31C1C8406AA02F846831D0B7FD", + }, + { + params: &SLHDSA128SmallSHAKE, + skSeed: "2A2CCF3CD8F9F86E131BE654CFF6C0B4", + skPRF: "FDFCEB1AA2F0BA2C3C1388194F6116C7", + pkSeed: "890CC7F4A46FE6C34D3F26A62FF962E1", + pkRoot: "E8C88D2BDCBA6F66E50403E77FA92EFE", + }, + { + params: &SLHDSA128FastSHA2, + skSeed: "AED6F6F5C5408BBFFA1136BC9049A701", + skPRF: "4D4CE0711E176A0C8A023508A692C207", + pkSeed: "74D98D5000AF53B98F36389A1292BED3", + pkRoot: "F4A650C56C426FCFDB88E3355459440C", + }, + { + params: &SLHDSA128FastSM3, + skSeed: "AED6F6F5C5408BBFFA1136BC9049A701", + skPRF: "4D4CE0711E176A0C8A023508A692C207", + pkSeed: "74D98D5000AF53B98F36389A1292BED3", + pkRoot: "876557FCFD59E660B26D2C607E81BDDA", + }, + { + params: &SLHDSA128FastSHAKE, + skSeed: "CD4A308C03D970508572C0815D7488B7", + skPRF: "F3FD6D2DCC7E5120FA544846AEDDED81", + pkSeed: "BC435C3E66E4C2E4FBC09779DA5F74D4", + pkRoot: "4EA0E0DF05C2457BCC81F59928433390", + }, + { + params: &SLHDSA192SmallSHA2, + skSeed: "3BFAED208B7DC795BF3647F86E4B48BF9ADB8D6784C50155", + skPRF: "A20311739497C3FCB860EE47E09EDE036F7AE8A939155BC0", + pkSeed: "A67856A81A6ADBCED7F1A2780CC48A06681BA5E8C7938506", + pkRoot: "BD031BC8124F95F0BAE2BECB2A3FBBAEC453C04A6E918FFB", + }, + { + params: &SLHDSA192SmallSHAKE, + skSeed: "915173EE0D17F30877E1D463E3DEC914E71F436867AD7615", + skPRF: "ED782E7033C4963A7FF0B67181DE0F0EA7EFABB326D40A86", + pkSeed: "520660F654D537DA6934F96E5EE01B24A2F36102F68DCD10", + pkRoot: "AA206FC79803E63850DA5E86969569FC8FB021B6C40616E2", + }, + { + params: &SLHDSA192FastSHA2, + skSeed: "45D7131C727DF1CC51DB85B44E37868215DF8AEC5D1B552F", + skPRF: "92BC5FC8A2969FE0A522492082E994DE1DDC90FA984F847B", + pkSeed: "8330589C20701AA9F11B473B67E1D67E1C6A2EB6C86265ED", + pkRoot: "13A3EA895C4EEEADDE8A796BBA5233F0D86EE5CBF2A6F99C", + }, + { + params: &SLHDSA192FastSHAKE, + skSeed: "855000FDFFFBA76962809C69432452F3DC79428F662C59B1", + skPRF: "43B1FC381C300B5ECEC7571B5DE2FCA16737E4C14911F683", + pkSeed: "124623BA6CA1BC1B0E1A303099E2A608B0AC41715BC788A1", + pkRoot: "9873C783378F935794ABC0313243EFC3F4A10A619CB1B1FE", + }, + { + params: &SLHDSA256SmallSHA2, + skSeed: "2FBEAB9A6A80FD817E7EFCDF834EFBD4F0A36195D7598408A6A151E93DE6A557", + skPRF: "5D0B37D1ECBC68265B0AFEECBBA783DD27EAFDBDF3143E4AF3E5057FD5C2DADA", + pkSeed: "1322F94917AE67D0DB420203178D591283C08BE8A1385A16CE70CD9FBAFD2AC6", + pkRoot: "40041EAB68A4A653F89CAB7585F6B410603326DBBAAF733E7E72CB6097A4A452", + }, + { + params: &SLHDSA256SmallSHAKE, + skSeed: "7D88445A7B0022F12E9E2D74755431505FF6DB1C38A8CE44864D34CFF1A12CE0", + skPRF: "FF2CD133AD00728EB29DD0CE881C41C640F2E28861555B59D4E0BAA0447BB542", + pkSeed: "87A133B92EB6C81771AE002819B4C0300FA63CD7181C805096BFB16067F52A45", + pkRoot: "CC785237C24D9235B6BC3194B79E5A9F953388EA745D7CFB87826A94E5B271D5", + }, + { + params: &SLHDSA256FastSHA2, + skSeed: "B8ABC485122BE003CF36D677BEE7F47EA1017C39D96D0C56A87A7ADAD24F731A", + skPRF: "9222684FFACF803D44CB98222C44B3C519698B798D8F7A759FE2FA6EF173CF64", + pkSeed: "0D50E82BEDB42E03CC967E7FD24C12777855A946FD49471184330F096A75B561", + pkRoot: "7FB65FBD08D05F24F20CB3875E28FAC4A52A2513C7EF447B8E9328632A684CF7", + }, + { + params: &SLHDSA256FastSHAKE, + skSeed: "3DE4B54A5F5FB98D6638FB3D8899355CC3582E8A397D0990CAD032D78EE9E199", + skPRF: "DA7F71D21D0182A99DE34E2796FE5DDE046D9C9E961DCE24C2562728BE7D9632", + pkSeed: "B3EF3825A515E0B2E4164DB7EC805B4CF1C7A2DE6E63D7DF359B99B1F3063F25", + pkRoot: "AEC38FF53C46AAD930166957CA0DB5C5466D0CBE9A11970987A230EBBB5450A4", + }, +} + +func TestGenerateKeyInternal(t *testing.T) { + for _, tc := range keyCases { + skSeed, _ := hex.DecodeString(tc.skSeed) + skPRF, _ := hex.DecodeString(tc.skPRF) + pkSeed, _ := hex.DecodeString(tc.pkSeed) + pkRoot, _ := hex.DecodeString(tc.pkRoot) + + privKey, err := generateKeyInernal(skSeed, skPRF, pkSeed, tc.params) + if err != nil { + t.Errorf("generateKeyInernal(%x,%x,%x) = %v", skSeed, skPRF, pkSeed, err) + continue + } + + if !bytes.Equal(privKey.PublicKey.root[:tc.params.n], pkRoot) { + t.Errorf("generateKeyInernal(%x,%x,%x) = %x, expected=%x", skSeed, skPRF, pkSeed, privKey.PublicKey.root[:tc.params.n], pkRoot) + } + } +} diff --git a/slhdsa/parameterset.go b/slhdsa/parameterset.go new file mode 100644 index 0000000..bba7ddb --- /dev/null +++ b/slhdsa/parameterset.go @@ -0,0 +1,123 @@ +package slhdsa + +const ( + MAX_N = 32 + MAX_M = 49 + MAX_K = 35 + MAX_A = 9 + MAX_K_TIMES_A = MAX_K * MAX_A + MAX_WOTS_LEN = 2*MAX_N + 3 + + MAX_CONTEXT_LEN = 255 +) + +type params struct { + alg string + isShake int + isSM3 int + n uint32 // Security parameter (Hash output size in bytes) (16, 24, 32) + len uint32 // 2*n + 3 + h uint32 // The total height of the tree (63, 64, 66, 68). #keypairs = 2^h + d uint32 // The number of tree layers (7, 8, 17, 22) + hm uint32 // The height (h') of each merkle tree. (h = hm * d ) + a uint32 // Height of a FORS tree + k uint32 // The number of FORS trees + m uint32 // The size of H_MSG() output + securityCategory uint32 + sigLen int + pkLen int +} + +// sigLen = (1+k*(1+a)+d*(hm+len))*n +var ( + SLHDSA128SmallSHA2 = params{alg: "SLH-DSA-SHA2-128s", isShake: 0, isSM3: 0, n: 16, len: 35, h: 63, d: 7, hm: 9, a: 12, k: 14, m: 30, + securityCategory: 1, sigLen: 7856, pkLen: 32} + SLHDSA128SmallSM3 = params{alg: "SLH-DSA-SM3-128s", isShake: 0, isSM3: 1, n: 16, len: 35, h: 63, d: 7, hm: 9, a: 12, k: 14, m: 30, + securityCategory: 1, sigLen: 7856, pkLen: 32} + SLHDSA128SmallSHAKE = params{alg: "SLH-DSA-SHAKE-128s", isShake: 1, isSM3: 0, n: 16, len: 35, h: 63, d: 7, hm: 9, a: 12, k: 14, m: 30, + securityCategory: 1, sigLen: 7856, pkLen: 32} + SLHDSA128FastSHA2 = params{alg: "SLH-DSA-SHA2-128f", isShake: 0, isSM3: 0, n: 16, len: 35, h: 66, d: 22, hm: 3, a: 6, k: 33, m: 34, + securityCategory: 1, sigLen: 17088, pkLen: 32} + SLHDSA128FastSM3 = params{alg: "SLH-DSA-SM3-128f", isShake: 0, isSM3: 10, n: 16, len: 35, h: 66, d: 22, hm: 3, a: 6, k: 33, m: 34, + securityCategory: 1, sigLen: 17088, pkLen: 32} + SLHDSA128FastSHAKE = params{alg: "SLH-DSA-SHAKE-128f", isShake: 1, isSM3: 0, n: 16, len: 35, h: 66, d: 22, hm: 3, a: 6, k: 33, m: 34, + securityCategory: 1, sigLen: 17088, pkLen: 32} + SLHDSA192SmallSHA2 = params{alg: "SLH-DSA-SHA2-192s", isShake: 0, isSM3: 0, n: 24, len: 51, h: 63, d: 7, hm: 9, a: 14, k: 17, m: 39, + securityCategory: 3, sigLen: 16224, pkLen: 48} + SLHDSA192SmallSHAKE = params{alg: "SLH-DSA-SHAKE-192s", isShake: 1, isSM3: 0, n: 24, len: 51, h: 63, d: 7, hm: 9, a: 14, k: 17, m: 39, + securityCategory: 3, sigLen: 16224, pkLen: 48} + SLHDSA192FastSHA2 = params{alg: "SLH-DSA-SHA2-192f", isShake: 0, isSM3: 0, n: 24, len: 51, h: 66, d: 22, hm: 3, a: 8, k: 33, m: 42, + securityCategory: 3, sigLen: 35664, pkLen: 48} + SLHDSA192FastSHAKE = params{alg: "SLH-DSA-SHAKE-192f", isShake: 1, isSM3: 0, n: 24, len: 51, h: 66, d: 22, hm: 3, a: 8, k: 33, m: 42, + securityCategory: 3, sigLen: 35664, pkLen: 48} + SLHDSA256SmallSHA2 = params{alg: "SLH-DSA-SHA2-256s", isShake: 0, isSM3: 0, n: 32, len: 67, h: 64, d: 8, hm: 8, a: 14, k: 22, m: 47, + securityCategory: 5, sigLen: 29792, pkLen: 64} + SLHDSA256SmallSHAKE = params{alg: "SLH-DSA-SHAKE-256s", isShake: 1, isSM3: 0, n: 32, len: 67, h: 64, d: 8, hm: 8, a: 14, k: 22, m: 47, + securityCategory: 5, sigLen: 29792, pkLen: 64} + SLHDSA256FastSHA2 = params{alg: "SLH-DSA-SHA2-256f", isShake: 0, isSM3: 0, n: 32, len: 67, h: 68, d: 17, hm: 4, a: 9, k: 35, m: 49, + securityCategory: 5, sigLen: 49856, pkLen: 64} + SLHDSA256FastSHAKE = params{alg: "SLH-DSA-SHAKE-256f", isShake: 1, isSM3: 0, n: 32, len: 67, h: 68, d: 17, hm: 4, a: 9, k: 35, m: 49, + securityCategory: 5, sigLen: 49856, pkLen: 64} +) + +var parameterSets = map[string]*params{ + "SLH-DSA-SHA2-128s": &SLHDSA128SmallSHA2, + "SLH-DSA-SHA2-128f": &SLHDSA128FastSHA2, + "SLH-DSA-SHA2-192s": &SLHDSA192SmallSHA2, + "SLH-DSA-SHA2-192f": &SLHDSA192FastSHA2, + "SLH-DSA-SHA2-256s": &SLHDSA256SmallSHA2, + "SLH-DSA-SHA2-256f": &SLHDSA256FastSHA2, + "SLH-DSA-SM3-128s": &SLHDSA128SmallSM3, + "SLH-DSA-SM3-128f": &SLHDSA128FastSM3, + "SLH-DSA-SHAKE-128s": &SLHDSA128SmallSHAKE, + "SLH-DSA-SHAKE-128f": &SLHDSA128FastSHAKE, + "SLH-DSA-SHAKE-192s": &SLHDSA192SmallSHAKE, + "SLH-DSA-SHAKE-192f": &SLHDSA192FastSHAKE, + "SLH-DSA-SHAKE-256s": &SLHDSA256SmallSHAKE, + "SLH-DSA-SHAKE-256f": &SLHDSA256FastSHAKE, +} + +func GetParameterSet(name string) (*params, bool) { + if p, ok := parameterSets[name]; ok { + return p, true + } + return nil, false +} + +func (p *params) Equal(x any) bool { + if x == nil { + return false + } + if p2, ok := x.(*params); ok { + return p.alg == p2.alg && p.isShake == p2.isShake && p.isSM3 == p2.isSM3 && + p.n == p2.n && p.h == p2.h && p.d == p2.d && p.hm == p2.hm && + p.a == p2.a && p.k == p2.k && p.m == p2.m && + p.securityCategory == p2.securityCategory && + p.sigLen == p2.sigLen && p.pkLen == p2.pkLen + } + return false +} + +func (p *params) mdLen() int { + return int(p.k*p.a+7) >> 3 +} + +func (p *params) treeIdxLen() int { + return int(p.h-p.hm+7) >> 3 // 7 or 8 bytes +} + +func (p *params) treeIdxMask() uint64 { + return (1 << (p.h - p.hm)) - 1 +} + +func (p *params) leafIdxLen() int { + return int(p.hm+7) >> 3 // 1 or 2 bytes +} + +func (p *params) leafIdxMask() uint64 { + return (1 << p.hm) - 1 +} + +func (p *params) String() string { + return p.alg +} diff --git a/slhdsa/testdata/SLHDSA128FastSHA2_1.json b/slhdsa/testdata/SLHDSA128FastSHA2_1.json new file mode 100644 index 0000000..7712743 --- /dev/null +++ b/slhdsa/testdata/SLHDSA128FastSHA2_1.json @@ -0,0 +1,8 @@ +{ + "parameterSet": "SLH-DSA-SHA2-128f", + "sk": "D5213BA4BB6470F1B9EDA88CBC94E6277A58A951EF7F2B81461DBAC41B5A6B83FA495FB834DEFEA7CC96A81309479135A67029E90668C5A58B96E60111491F3D", + "pk": "FA495FB834DEFEA7CC96A81309479135A67029E90668C5A58B96E60111491F3D", + "message": "3F", + "context": "", + "signature": "BD40E6D66893F38D5C5FAD99E4885329925BB207D49E62BCB9B1C4685154A8B32E58B70C7AED0E28507F31B49EC7ED6ED6DCB8DB2DA90FE938994D75C80E6712F2421C22DEF8AF88906B768333E7EBF6DDF7B84DC01F06731DD640CF93F57927BB56F9DA9D4B2ABE60C81D863A20F8E5C5CCE74326D6181D01B74E3CD7F794A98B4ED7A791A1B77C561A6E7AE64E4E17481DE4CE7E26065D90AE21C965FEBA3302102D7564E3B7414E1AA62271E9B4DFB42C57C44726AF6FE7F3BDD486D7D578B4B4BA8EBC1F5D7243F94D2D2D4CB55B7F95C3020E05A6CCBE12CBDFFD6466B5B34369FA56839A0E05AF5C6613E4A229895CF5A834880A2C3937CC759F3673567F39FF2B8A0613EEE33963B06D200181F3FE69B507F2172E459B989A8819C7EBBA3AAF31F9D589DC0123012B787B60DCE3DA8A76D1A3476EF08FB8ACB72F6C1F7C8B6929642822EAA13965D6C1F3C58B600CF029758C41E26E0EDB6FD5F2EB13EB91D95ED3AB976E5C6DFE1C80879B8BE68DBFB9F8E2E60D822D88DCAD48EF2EF89F5486FCE1506002E7A7AD8F0E58374E3F82B6E72CF0CD04B86BBB9F261BEA70C785521BA607B8A2DE642C6EB84F691307618C60AD713F7B10857D28613A6418DD1297544671091668F5E8EF5ED296DF37CC6E45B36F261A66B4AD8BF55C63298A6FD79B9A128D44DF4818E613B783DD8D8116DFAB297F520163A15F35A4B96105D7A695C723F11E38964C05F5840AD333FBCF1862B2BFD0433D645F411E73C6434480E7C55EBD1B4E1B786A7A333B8FFC5E77CB303A3D093FA0D18DD223FD3CC352EDD11F95200A2D6791011B40EF6CCEAC57842961CDF74DCC5CE09B219A615B08A9BB92E2F001B7E5FD87D092BE800DFFA75D1D10AD80E543A7809384C8C857780D66B9A7A9A7B15F72C1AEC5EE6F8CAF7D6B128DFD34E26AC6F5267052557E2AF504BB5E8110F28B8CB3268900D37E5E53A2642FF7AD1EC4B690A99BD62A3883537E3D77F80B09D27DB2A28DA659A3B100E3B65088E837826FA707E8E39149056C3BB13D957486964351D88CE1BCB69968C85690C959992AF98609AF5ED34A681FD32F8D1A5E219D38D4CC228182697C9389B2E9B5059B8AC4A280DFE3D6838D879830643CFF92CA02A1C9EB1643516A31C55E0E8D0F9CBF16D01FC0B8CA214259DED8CAEA43A013A645F9CE5300520066CD2AC04BAF8D49AE7694D40BB60AAD324569690218FE19DFA58EF73D62A831501AA25F7EFB5FC9C8150955FE6524DE636CE526B100A29E6E48EE047F33BA6C0BA5ABD5E5720945796A57FC389CAA1755A339F6C584B13D6971833C9E865398C8BF486A5F99EDC9E5D69A04BA1118A3A9140CD52A951D283242B5583282DD5CA1AFC867C14947F68F8D3D91105AC4AA565650430BA9334FA8A8C5B76BAB24D1BE6BBAC8A478B89EF8E9E8B33BF38CFDFFA1D07F984036BB5D9A71031A67050BF451468D1622AD99EBFD71B7ADF09D1C5599C347A8778776E7D9DF5495728FA6E8C6A18FFD7DD6CF2CA7BBCC84B12EE03D9AC24F2EE35F4925161D41F61EC3D51D9A96A1CD67C84E7350DE302CCBBE3BD56EB1B1682FD60DC5EFC1AF97A9A8AF08F088E9B561221111CF29E63A3E7715C84BB0B9756FD8D8A92CAA2EF658E268DE024A54B9B6EBDC681AB04415F5656315B35055160DB4083D184893E8D4C870B803394BAB5E38F5C390FAFECD22B052EE4461A624587F6EBE70B90A840540F009715B0AAE502D2811BB7E345FF2F4F779AE981287BFB96B9A73B999D7778FC47718D47907F60B273C37DD1E7ADF6FAE38F6BC5F392927F18E742CFCBE81C0A4C8C75403361F1BA7F867DD94F4D22AD03C38D554BD9E4DE497ED63C156BA9086F4C8B4D087529EDCC0295A93AFB5373BF46BD04B2E5EA5863C850C3283B3E7524BD5E2ED5937742062EC144E829BFFCB9DAB3F9C4C5DDFE8AFE51BB58BA2D0C8C32393AE9F49764ECF7BBBEE807B98EF8FA9B18A88731B5388C717F321BD4761A74606226C5C9E3E203BF47B1724DA6AA13FF7267B99CE68523050523FC4B8A42FDFAD0F4A0EC0340BB6C1A58B4DB63C03589632485496407B90169AE9F7C7B287E0841B6CE570942FA518CCD80A355F64FD5F739BCC31BAD3C618B591F0CDE79614388B538EFCCE119B0884A850FBA18BA41F39F08E84D8E6B38D7760A39DCCCAF4A031EAE014C6D6188FE0333D166719D275BB56056EA4B8203673F08BB5C44C57B209AB57C17475E22E55B06453998F919557582959376745FA1E348E9DA508CF2E96FB4FEB4B903B36385251B34F319D9ABE258E0B8318A7C9D45647F99CD4A317A9CF017ED9B341C1FD501426BB6C04E12CFB5220AE2A1DFC02DFA2BE4AC859F837EAA1FF14D99D86A26FBE346F869BA7B662EE5B69FD1B8D16FE352BC5720F402A009C649DAE7DDF6CEF84DD2251D5F97C91ACEA5326DBFDF4CE695B5C5908B43EAA79EC1670D75665991AEF8979747976173A5875C912FFF4EE76EB2FFAC233B77FD330B6F888CF0393FA381328BD9936A977DE7240772876BF15A3009ADFD2AB9870B49E79201AB912D57FC237F1D83B63D8EB1EF7EC1055B6A4D2755BCE09D9F2BED40D36033360CB9375A3A5EF8BA045A816914D3489DF7B6B2A2FDB5FADC6B3E1A9CF4063D06B43D7ED75A8C78674CE7858FEC0ECAB11E1A041FF986A904BD84968F299419DF5F960C2736E75718008F9DFCECDF20EA3C9A79190AB27033989A40D3B97D89FF662E63CC0B639E77FD3E983239D8E59F0585B12C803E1BD3A5865D1D4D3F022ADB4DEEE488F2D2C08F1997D8601D702CD9E27984E171A6364C6887E8A625A23EF4988FBC6888A2A49C17CB596E4C415BF2CE9EA4741BD00E65AE90B8C53866CA49F20E575A31F011D22ED8DE7A41F71BD9BB9F7CE42E0A5705C3498415C0CE462558366B00DADD9DA6F17C666D46695B250E651965E814EC70D78C507E4EDB965678C1F80CDA6C7CFD720FC133582F03F848849B261892696765F327DBF653CC7C88FA9ECE9CC172B2E91FFE90CAACC876BC26E44B2A4FEF46A4E2BAD72A55D268E4E99B95D13A196FE6ABF7DEECE54C7677813EB04AF9601B323FD27D90D8701EBF06E539796E68D320BDD2A8638029C6612C519CC44D1AA2BABE31DDAB3A83714C805B98731329CD1FADA30E7E690B949E2E7417975BD83D8130DE44D186A90D0F435C78FC4EB6A02ED891FD1C67BB4052A6339CD75AD525D8F84B4CEB33900F7D1214C44B1EB05C224CC9569FD58CA77EA9193E591E658058E50555C63D98F8528467F134468426304D9771346AEDD3D072059103000906D1843B19B23490070DD6A9F5C6185C34ED9CB73CBFF1599662727CE40795CBB8FC3BB669F670FEC731A226AE12B08FA4F6C23B3C3B2366490CC023C4BE2766168E776F1C186DDE099DAAC158D2CC085577271F7965545F2FF9EB02C670E5CF5625722A140E96291246E941E0B9D0F94C05E66C9EAC61A1E6D5265B9491D6E0FE13C3DAF44BD6AE2C3868E262767AD21106831FC99101E3A8F47127FCBE648A6CFDF841686B27706456E9BDAAE54E5689EC2FB6274749C955AC62892B62AF27ECA451EA199D565891C8C11E36F24B13A74A46D5194272FEB4689F598D2BE372BCA45E177B80D7C352AB598AC0EE6F72C9531074F98860AF8E7D0F49258F50414525715262D689B13164EEC4B8A0419B5E6E1C831249375F6BAB546102CA90333B15A24E543A2579B074EF40E9237150561405804FCA0095D5C4D8D6456DF31DD847F517C8FBEEA4CA8EA88CCE339A4C7A565C43674BD55C58521E4E50E837A76C34CC1F625AEA9D4AE909BADA0C37B5E47CB26562BE2E37402D2210A82CA8E397CB2B88453A2F0FE779E7C6A74FA32B80A99352790B6D871470BA75506F8E6CF0D7F9DF4716D86FBA40D2F2FB7C3C6ECCE2B534B4F693BD5A6DD7E1DA3A1B1209A17FEB7E9830E67BEC26F277921D048F32B9ABED990AB2A7ADA21374D1BD64D3EBC5333F437492D12D5E89798AA7B83E6467BF69E221705CB06CE8B2C96AE7F8D0B41AF3DB1F183ABC5151C02C3CFED01F58266E3C2E67A232DD2D11F8573B670A974CE7D9C8F6FAB7D70C7437A0A0EA38F094A0908F5162E3C63C6FE09701D4AB6EBEDDDF8CB52D8EFF8C174A051A841FC36DF127501B2DA18F087B98B0F80DEE70CB7670066219289E9CA9C1A9EFFDBE14DE19D20850D98149CFF50314B91891097FAA023D699009BCE636E401610E24667AC3D5B41ADADD82872FB0874BC42593134086538DB3CBCA27BF7B8CED845B9FB7A005E813E38971F36BB793E96CBF65CE3E4BB2B20FAE2DFBF63B84962B7D7960BDCAEEC39FFE5587586C5A4E080C4E3C9A370BA4822637524925A4AD8565771E1EF566641773410C6EDBECFEA9382E9B19EAEB05DF8851220DC24B4211D5AD427B8B4824ADE2BF31983B2B426D7E872C205A0132C6D413B53CF4B975CE36749A75994589C34ACC9A87B8B147DC886CC30E02355C84579C64C1D9A466A44BDB6BABED60D143CD89FC9AAAB0B400154E4FB1AE0915A720C8B5BB56875EE54F44ACB9BDB8D447D64A407956FCE1C700CB86F014F398A34466C4F8F8D9DB8B8EEB16762A02B314A05799E7DCE5C1738EAFC729BF655E351E3CD6F061CA4CF25E98FFB486B6DCCAD82FAE13896B7DFF055C58B2B7643D40257EC1FA091C654FBE16338A02276AF20EAB9A21993DDECDBFF5C7F00EE9FC7EE9E5AA5C50C43F657C7E65B4D865B1822EA0CFA3010F310CA66174EB341C82E22D797A252C6A8DE77452E8CC6117673B10041E8165ACA650B5A6DBCBE29601BC570C13C7D8DD57679ED3F9D459F4BF0A29BDD476FAC13CC4CFCD9A3C65B63F57A93DB350BDABCF697F069DD909B4808176E265BB268FB9BC4200B83BC7B18D43DAC8997BF4834A14C4107F3C9E597F77AD3313E670159D94ADBC46DA1F2B69642B7FA8E1EBFFC228A222F951BE0ADF61422878E4939EA32B83242692A140D80DF11D95734E69B952FF7C6716E0B360BE4D0BC3D675E0CFB721681911BBF5AD8040A24901159B1D805CE023E731CCDE39E2B38F096578C9A974AC0CE9A28D923515C1FD369CDA5F3FA35E7358EFDB91A0771F3DAA64E685F5E24BC819C93486B2871FE59A0A2749A15BCFE36C4AB4AC2ED4803015FAF8BD4C309EE883D4F58131F778FBB608F07C3FB62E588CC47902B6A0C646676640AB0EE1B342537E9376CE5A328A052F8F91D66A4AB9A12263BB3184367731930B4789490BF4594EF7A01636BB2775A33E8FD6BF252DFBB4FAA2C310B1130FC0443BECA6E04CF2CEC263B42B26FC0529C5BBDD2643D44F57A392273866626DAC471CA18F115B4346E850D98268BB992FA11733E5F01D2A5753C2E3163F4BAB64F9A19E719D07BDFC51176A327851A7320DA9F9A1A11E57B6D374E26C131BC9D94630F9CE23EA9AD40466A0C6823E0AB5F3282B109B2485B211AD9F62A79E842B963D3E9399EBDFC60DFF8B12266920886CA26F214048FDBCE5282E652DD4C7F0C48A9F8B9935512A89182F81777BE6D4B5FBAFE895DBB4319D32EFC6069A02B87C4CDFEF1955FD3D808442C966E13631A269D206C5CEFE96C2E67FC8E1229FC99EBDCA89AA803C94052973D560F50EE33E74574B5B208A237E2A83DEF9E15356054251A5804577C024153B42C589249B630A9B49C3825B5B41925388A4976F1217C169291FD65A10A275BFE81BAE5C1CDF39539049B38DA2F4FB87E7824F2A983DA6A4FE9B4FE5B26649D6BBF0A81BA862C90648BF8D8376CFAA81BB9F97B080BBE5C6E899B8C9743144DDC8CFB705588DC63741355DC691C7F58D73C9D9E528784BB43E59D669CD7540BB0D8C33EA9C879DE8CB549EB0454172409F95B0F97E3069328AEDCC2518461D870B4C9B7D8606405E46609A8AC9B9A53A0C57B02D4FB15EC8C6B6FC31369522E2BA2FF870606598F5BC5877BDA4198F0618262265BCBE6506BEBCFB463E92AEA4CE08660AB55A3008385AE75FA4746EA8DDE051B9900AAB1F5015F0EDAC6F6C4AAB0FEA3741512BC254E9E2D07C1EB010C88378FEAE0988EF4202EEA1238ADA11135D085225F2C8B976E473AFB04EE2801DA342B7FE35FEA3966F79C4D167D5AEE5E885C5418DD14A91A06032916FE34EDC40BE2AD9F5505BE80404F62810EBDF8A7C96FF7DFBFCCDC32FBE13A03F2D074848495AB0D766D5B35BDD5A841B566A901D371BCDA153F52A146AC59897F5F1BA49D7233BC43452252646C15612F5F27741A97D6C72E892E58DAAE7F6B81E8E3286746211F530A2C302CB4AEEA63242BD6800313ED6F5EF007971FA3DA5810BA9E5F6C98B1637C846AC89E22112A998DA631364715FDA957DB016EF26A6E4535A2B3F3FCBED6468D65806619F42FB514C7BBBF7F08C1A15266253815367014E9B08EEA76641566073879C11B9C6A760544F852CC35705956BD40BEE059AAA1E6AD6E1AF24F26F48CFBB37416A10B146CE0DA60AF7148ED9E8171D6ABD085312AB72CD03979D7477552018A2DCE6164C3BB358A1B7687D613A82ADC25D74D39E72F5BBF136C8C7A82CB3A7A1E0559E50ECDFF699C8CE3D6E9005E03E035075C04AD101CC85C5EE7EDE82880DF1764350A49DDA44C6FBA1E6DEBD083A48F7E94238470AFE01AF6CE430E05822114F24EA0B85541D93F99C414C1385BBFA4745B4EA0B3FB45A94B3D7FD0D6A5FE05C2F32616059621A636ADBE9E8BFFCD4139B0D5A0B6698EAECB449D60E531B8BCFD634D4832499B36A2216F1FEB7B939CA80AE87672E4A7468889F17C88F529E9EA74E1CF1504E81A2D516B0F2ED4794A5E21FD4B9CF5B039FC5FB0F8F77C55CD927BF157ABBA24274D8214A1A825C2F3C8D252FB2B765AAA0A3BEFF0DA54CBC829529E19B02347B8F91BB92813756C8413B6DD145C0146B0C77CC9D01C6FCEEE99A84C28E3A77A14B0B75B56AE8A648F8091DDC917C4EEAF777CC4FAA4F3F9D117CB0F0DB6BB300E913737A7620D1BCEFDA18DE324BAA0F1867EEDE9F7002D495C8AE27F2EF4164782A04BDF46AE627817330DF16094F3E29EC837AE2AF472B867FD9D7F6AC4BF058868AC15D0B7F0D84A639B4A71F9E04E4B4E84982D8E07D0C2C54B78E711E0710F6DD52DB394E3B311A38B64AE960D72E2A655E798C16E7F8D48559B9454DBBA942721350ED975EA4CCAD19C0E422EB9E269117EA62FF7FAD2F8D75561271661B2788E2B2DC1202C5E306BF777107B53A9D65C7800DCED403946E98631211DA326F5B213CEAB5093C5D2E4E2054D50DDE8AC2E42425BD3ABAAA88F4CBDA59B926684FF8D0F588BEE61822CD88360F8EE2A8E55BADF99D6CE950F3FEF895CD8DA4941848B468E4A3C388500D808CF32673417271AF4C6044C4DCA635198D4B924B15BCFC80CEA2B97939575D0D4B6A36F3F8EECEC9BAE05B2A61165E99714A7136C802AF01380DB9E8E504A96BFD015B7E01867193E26B6BAB469B47552C3F4D46DB07340F51B5FA9435AB04113595D786A3409E7E891AD64F154C8A98463FB7AF7BEE76A185B9B7C0EEBF5C7C1F6D5EEE1EC5DDC66F041569FE35B1E343264096F8C567D90F68E21F5B4143D1436A1577169CF42BA5D7516BBFBB74C96112BD80FAD78568711780022F2C679B90E39B97AE28714288890D75F4EAE850C606A38608A23968A7A09A1766F596AFD753AAB473EC5BCDE5BEEE72CA957FFFE47035036E6234C69771D472FF664A3952F9947737B4A119646D646914701EDF2E0DDC4F929F02AC36B1677E49743BF1D715520966696872A9ADE1A5115B9C4A6BFCB40F70BB88AD2ADE2FFC4E5CCA5EC3DBE84EA5AFEA71626AD0BD8BF493006E9782FF67BA6574AFFEC666D368C8E75D9B28BA769246ED84B2D2D1F3240F4906255175837309BBCA09CC6A37AAD50696EF57E6405C1B79F72B866912C17C0C79DC22819BD4389FC43505D78E5DADB0870AE822B4422B034197248E347F8A7B23ED9BA7E86A8C8F3B8C7FC1FCA347BB2295D799FCF603C4A34272D8118421D50FE596EFDF6231B3BF8D5CC4FCB91D4AA5A7719710B6B1AA41FE7FB810FC398A87CA557ED09D56F72D2E413E558A7EE4443A1D48DE20D8F11598BB000FD0CE59F1864BF06B28D154DC2368F57549EC37241DAF9DD776623B79B9C02B81C31C187B65CF29381BD9B9A6963C30ACA466A8DC355CCD024F16626C84D1619BC617FEF6EDC8FA322DD7E0666C9FA6543847AB8222C4325F24232375B7CFAF667E16F76295295472CA29A74391C3484B024A22DBB02D4A12997C106DF0B1EE051017F8D7D20FFE8DEA674CB700CB6056436B5D80F5E25C9EDFA20973DE2C27BDBB1BF858DDF98B33A34EF25080F628400383310439E4E9C8D1782310C23832B63A1545E8D645B65ABA9D88091371808116631BEE7DBD8DA64F7090CDFEB0CBB402E152B3B65773064AB4349CF6C88E426BB6AA51C8E1A80096258767E6B67878C69C5FE0E2C3573ED65D28FC99BEC39132E2ED1BEF3D212E7C36B8F2738F723F4CE7A6D482185F569B2D69F64E719C73CC07F2DE9C8AC3F198C928EEDF178559CBC19C01B925291A2227E5277A00DC4D0FA68A2836E9D0EDE7BBB6B70FCD8216C5CDFFBEC4118A8BDB117EE10833B44D64AD1C672201B593ACACFCCC4AA2C599DCEBC44F37D4328BD82DB4D8D6B452854C08BAAAF96AAE79AAD1198206C803E7196E95A2F2A9E443D3AD1D71F18658DE5470E0C2ECFC4264D34EDEFACD6DBF02CB27127623E38653D3102F3FEDF61817EB2FBD5F134485CF4CEDCBFADB96F4AECC890BE352A3E318C8D57BD1A7618F0DF5952F5CEE62E95731AC62BA1D0BC1B5C6D4589904C5CA2235EFB2D6C2681A6E7C1D276907DFCC7C876352AF7CBB3DC347016AD7C6B8E7ACE5F6FC975C8EEE2F71B037539B2919A0C3F505FB7E91C2F7F70EDDFAE0B86CFB6AF6965061C4D5B2F5C6AB570E891890FA92B056A052B0A1E7EBE18123D14F64BDCC9579FEC83AA214AC5B85F416338BCFF924D247E99ABE3293D8DF2C10A80DD1D354974B2D0972E4EE19793C6F079BB7A7BB913691DE12FF5F147868047632B1186D5C069F9CBA7ED09BD98CFF31DA8698D7ECEEB7A12FCA381619BBFE33A1A0DE2C8B14AAAE85FF17133373C365B198DFE65B3D4326B6CA8188C35F1FF4EE5CEF215C3AD3744BB53A56B1FE8AF8B408C2CEF1F32A6BF35BC4DD4B391787C2E27B51D7E49EAD873A3E4E393008BA1F7CA45DC27B89D8F39F8AD12149FFEACE5D518BE2D6D74F93A36F82C69936D048798DE5E11456D198E617D6596BFD26686451205246889429D67B68BD28D8DDF3CEA2DD5169FB58B64446171A5CE7E8FD79846CC92B69DD03B04468D57FE75CF4B0792093CC1F7AC7BE6E4708EE636DB61F0D0BEAAF4ACA0129D09C4DA407C7BFCF1C7FF1EACEB064F0D8D541E97BA8500FDA615751DD2320F211940EA2060AF8E1DE9661B107C239DB9E866555F85B1DA367940B45792CAF93523D8375C7F9AC2EC5246B3567AD3A3E30668B0A64A29DC828A8F7F7D0F68603757661D3E30A3195344D1AB9126B0CF0DE7E9CE603E75915B45CD1E1A1178EBBD96EEC5FCD2B21B4208B32F3A55B2C89D5DABBC1B62FF40D0004D34CF8E49A2BC2D7650BDDA3523C1EDB98B404BA87CF2D55A52922E6837E5510B782E28BE3AD4B67737CB9656F0F15CE3B189BB1CDB4B6DA5B3927E289B464742D295F04520C7DE50C3B710472B5DA75F58B272C609EB918D8F564382DAC244F2BF9F48CCFF77205114F72900E6BE86AFD590044E8F26DF81CCE3A57A4A7A6FAA427411078BF40289B4E76A18AF73CB9CC44DE82512D74E3804E0599AA2660F949FC52A832AF6EFFEC09A9FBE9CA05A418337CC237D6B5DF9DBE12FE1867A8E41FA87513AC0C29FF675C0A3DE70A86D50E6FF8CAD5BEDEFF0083D2C74326E250AADCA2CE44A3BD4F0A410BD17385E6CA87D3ED1685FD4FFD92AE8FE47512D2B7226A0CA2CE859CA4D0920BD48EE0E629DB896A5869B193F4D4C02D5BC1DEA3B8B1A6A182FDAAF8BC165016BF0B2A95571CD84EA116BB333C37AF7F915ED5C02A307B3D6CDABB71CE3314AF36B19ED69986785DA64AA1D2419F2A4D569C69181047DD14A9C3E112B7AB7BFA8C5BF4B14DDB0226D1E318B0E3319430067D5E4419E051B0B9AA6B126DF7F7685F7A947802656E7D41B87FD62DC509E2DC36D7253D5652B33DB837F6932C116580CF2EAEC35FDDF000C49344CE1FE3ABE380AC6FBF174E9C57DB14498EF251C3C69DFCEF50F29CFBE783F28444882C998454A751E9D2F256252F6B1C12C5B177E64B3D297D3B78A2363B3D2B6E1AA4C5408CB414FECAAA1228BB014384D753A2154DC0B13CD1181AC4C78EEA7819375916EAC65FBD67C83FEAF1397BED4F7489C4CDFCBCF838E3FB72581A832B6B7F1494DF2F56535E7AD488DFB6677730162C34A130ECBD7D70B725CAF6F17060528E24C90A161D15DE7DF2EF10EBB82B0CC830DFC1E0E28C9412288529EA94C9C4D3E60FE2FBED0CEFF2AB6C09DF3CAEFD5D1936A9E99C9BDE73A94E84B80BFF870FE6FF8A79EB03ADF7DC0571E666FC6D645FCBDC5E3D3A00AE3E24B82D18F45BED70CDF38C3965914CF564184AD3994369F436990A44CEB40ABDE9737D160911EF4EDCBCF24107CC6E47047A49ACA096F84CB2BE19B97E388F5028E821B4951AF61AC4FA8A4E1DDFBA6A53C189BEED5493684ED4BEA514AD69A65FD4F29E3E4DA216319773E5EB1D0B42FC7C7DB488266AD4CF48C7C3CF4A229FB1F31DFEF70E0984D933CE90E8567C92313213FB725EF5DE07CFAD01366B2645E9059FB0DF2F31508015D6EAD9E964E94CC93A6FDE5315AD5D6607C3D8C2DA588D06E0FDE261C4A98E9BF9FD6AF3422E607C4D2A3027D56ED8B0795F93852A8B9EAFFF06FADD041A7AF1DA0376D3F19ADB5C81FD513AA9BA6AB822AD4D625D2946EE17B0CD6DB8F4E01D6B98BB535078C72E3783F55BAD3662858F686E8724B78E5EAF7CF7EBF5AC6B35F4E9F59C7FD514F7AFCC10A783594EB1979F37D6684DD323FE90851C4FE394E79A4369281F1C9912A54CF5A6EA56A9F951F9996FCF2E08DD2F3866772CBD6E9CCAE5ADD5D6D180352E254E4695A307C71F65C45443F0DBC59C2F46647E02A29FA767C646A7C432A9EADC144D488B5BC40761B1B4EB10E09147656C3A3C7D3E549F668FCDD41E61002C15797901FC7D6A7E2F6C1A6BA3326AFF9B185E0CA3C312F5661D90BE182FE46BC759405776E08950EB5C3CD327EC52B01C3AEB7B54B9BA3FBB7D4C66B303E8D272D06C3E8483DD928830D604837BE6E8D4B9628DB505F9E233CF8A4639457681E0E9210AB311241F54AEC6A0BAC8177A6298BA585460E035B65812E98CD314845BB172CB645AEBD6BF058510DBAA67098E074E2B2840B3AD835CB58687502BE064B590354433BF3B31A71C706349A4B1A5CF11C9CA8AB67AFC33C69666BE07AC9DB04F4C214CBEC76C24B015DA97301D6D247319F9E5BD48361DE1F460F02274C7D6CB8D9FBA8147BA3750000FD04E353DF79206C47BF8148175C97B068480A40A9CC5EF2F46702FA25B0159F5E666A6605735CE0CB7E96236C84A6D571A381E5C78997FA8EB0CC9CA632295772F699C6744D9BCF5611DB9ECC71BC62790E8F427ACAA966DDEA6A565F0AFC2E05531F5B437E577E642EB390145385511B22901710BBC1C754B24CC60CBC594B316ADC8DD247DE2B9429F383045E0B4A3AF730AD0D34C09E9BC408E6ABC4318D516C9930CAAC7D3544278AC174DC22C089E5DB40AF8FE33EAC9417064BE0AFB8BA64BCFCB15D12B12F29B8A605BD26DACB023591E2C439DA8058B0D8770C3AA1734404D4944515227FE63C3B1566CEF9ED9319B09115F2C5E3A2A2D24F3B39BE9EC6791B0DD736ABA33ACB1A56D64E5215918EE86E66B8FEA89B9F0EC366AE139416811DEAA92BDAD797BB8A81C9B90913ECD79A794EABE510FFBC6441AB4A6450EE3ED1E7E90FEA4043AE945D36F2F1B83EE1FEF6AB31682DCB2755893C0A27D05DB584EAB64680A8BB3D38F8EBB4EEE6FB85DB0597C419622B4F7F0A657ECDECBECB56590C6B6F20BB6D4622C851820DC3E772444066E89F939F5B3CC9E0BCE4ABC3B1550E15416F5718319C279129E8A8734D557949EEE8608ADD8233EA8F48D30FE3B1252EF8CDD90AA548CE25BCD13DB505E2EE5E4866FD6A66F3A95895D487431B0EE268E5C43B0B79E605C80D08DC6D8EC6902CC56A5A78ADC002B16655939AD60353781617FAC8056B79A50E80DE88A52AEC69E22FFE28B823DF1FE2CBE9963CBA9C70385D1D0670B2823364B6B9E633A2210DBDB7516E60F22898DEF3F24545B3D6B9C2C73F2C086F5DC68FE73BA5DE35D4D9E3BF973B9F411EBE65608EEC1727C2A1CD90ADE60DE5C4F35E7934D767AC187AD8C54F92F9E3BAEE15528F52E3EF8125056157CDCA91ADCCC71BC6BC77ED3487FB176924D28348638BF02455B137B66A50F6C8F3872996FA6CAC3C3E83A5416E87D4389D28F4A41B5AEA51F182E4A4D9FA444858D169451E2C2CFBC4E0EDC733EE05207F758A319218F5D140097334A705844DC63D61AEE52003E5A674F7563A7DEAC6B54F93F2C7340C4AFEEF2232143FEE8CB0BD3C218D682B1219C13262665BC85D015AE65771EE6FAA39B4F58F0E7AFB1C90B9FD42C8C6C864ED1F9E8AFE0D08AB7611DC6231B6A2BEC4DF3F3EA10D9C099A6E4F6D41570E3BC17DA7897B78F7FE7E7112A1658972D31D5BA31C91A8B0ABAC7E5443F284C0D76F0A8713633E7E4DD2F4D936C98B479035228372FF093282FE0C22608185F18439907DD2B7647ABB1EDFBEDFE19E5C544723C9D4B49F5D1EFD65E4F51EDDB06322D378B73718F2A63B9ECA920A120F8709DC7A3F52F5351BC004B6CA08230D3FED7BDC366EF841A0E2C788D7B803056BD8B0BA92395A2AA9C446665BA77659A3C3A29DFE8C77FD77CA1BB79A0EB5E861ED0AB23A3C2E9DA14E6668EF03A18295805C3F3402CAB06403707423B6B4FB13523DF22D57A1E5595BFBDE7155B3EDB890B41F2DE52099E3B779D5EDFFB99980BE963DD13E71426EC98580FF1AFB85E50A55C1A6786F583820EF4D3771DFBD24E306DBDB5358829F5D8C9510EB9B0361F246EDD78EC3DFED505D51AF3787BBCD96EC8617BD672AAFB63939016543661B0518A0A1DDF8BF0C121B07749767E9E1CD70880846CD1002C0BA06D6BD0D8468823985F6ACB51362A766EA40F8267B0BB79572B5D9F2BE12873908F2927E08391A5270271ED25396A93BFA994EB3FA55D779FF42546FBA72349157577E67A87A97D4020E455E252505E3EA2D33EE56ED74AC7224CAA24A65DC803AC33CB419EB7415A9846F4DE767A134247AEB27062ECCB01DB51CC8633F661F9F1F1C66A0242D0D017DE5C46951C604B6E7CE2EE4EB7E28C4E9DA71E87137AE236AAE702406003604C0945FBC49A68F55D36DC5F5AE9C5FF8C617BE64F592E23CC5A878DA7EA68C823BDA36755F9B3677045B1FE1D33CCBE7FAE98309F0688DB55951F1334216F4BD68898537354A1D315766EF063C9F19DCA292002F58EFC135D47F81C385C61EE36DDFF51C4722F3B94A7B6F520466F55C327A82E424C340A0ADC426C8474B3D32E257491B8CB0BBDB4247EFE7928370AA7706CEF0AD5EB2D049C6EB69259B45C46E737E96D292EAF346D5045DAA4B81220D748DFE302FCB2A8018A8A1F77E32F6E8CFAA33D952CED8F090E928977F08BF1961D07FD8968C9A7114C5F2F180616BC0176858B1AA2C78DAB2AABCEC9E2C6E0D0E6785579BECB9FFFDA409E9E9719B1D961F25FC7F6791356883DA6B302C44C4E2BB9A7C6EBD72CCBB61669B476E82B5E1C27B319A83248E669B62967D3C94D18E0945EF47355F86B0EFC0242B526686F4230FA3DE5856232BC2D74BA2C7E43A684DB82B8D38BF5D76F4204D3B18C1EF928B160CD139E66EA9F1A619A2871B2E3D7D62A21C30156CDFC2631024A42946E655E5EC7CB31202AF99BE088065F3920E511831E483C75EEC489DEF905B82B6ADE1E8D15D31DCA4E492EFE66EB2ACDE2AF033950AC525B0B2D5429E47BE106DAB0D69000D9903ED88CE61DA1358821D6C560EBBAEE5DCB21063A68C94903D1A58367830D1696C5B915DD95007BB0E97C5D200997AB525190E59AB466B477B2FB940C00E0475459A984F351A14CBB610EF96BE2C92A03749D5C8D697E3391D4F83F5D2F0CFB0CB6734FBC0776A2B3AA5DE85F011B8EF100A2FF2EFDFFCD39691BA637B49BF7E3EE5585D0946292B5556773DAB67075B77C334D40DE0D278FF2671084CFE0A1E27E5517A00CD572EC57FBECA8B4CA4788AF28476D661B1AEF5A8827F015B36B1A6C5C21A23748F9156D2DD6530607FA8AD491FE2FCF9C55C49764B4425F6AD99338FE7F66C65F1259FC9734F7B1F37A92F4115F76C98DF5F7DF73F98CA99C9622E47A91BDEF7CA74EE76BFCF472D8CA9761027004AA297275AA8DE2DA73A31A29E104CDE47E75BDB0B9742CF647AAA4A7343E156571D5002FBE95CCF20FFB9993E38134542F19060A969B9A9FD20CDC01975DB4DA89B9357396521ED0BFECAB974C4A8114030317B10C41B3F12BC6CEEB3EC28746D6891497E0BA38E3077C1C77786153C616BEC2C9E23F00918D904DDCF1E7EE510713528224E87E6A1D3BC001266AD1899AC695CAF7489AF83EA4829593ADE85B7615EE220EC6EEDF7ECF17B5A1AC620FAAF18BA36FE8B62EB31AD9168C104CB56ECC1B2239CE108E403815E47034639EC602AB94630B97995226C166C36052D6B9DBE8955B1F5F7E0B0AF03AB798EE0478E6CD45544503ED86A980AF2EAFF29D8478FB84F26BB230D295B05F089046252331680206E8DB7D6FAD6BC6580862E492193DA4111DD7E8D8ACBD4DFBC6ED2A29418A414DDE900220F9E511B632C9FFCE26B74D02366426EFDE60E49582DE4F9676A660AC778F8D4FD8E616A25433249890264D7E17356E781559E7424EEF88B8835B83B43A0C64DF1CE9A8B7F59D26C96EFBBCE496DE95EB623A021EC17C2A7215A4419F0938DB66D50CA545CDF0AF1A3E5435AD5DBD38CC9F5F60A8F63B5876D90F627312271806C3556F5C910A9F1D75946ACF0912973106695F9DEB91CDAD986FFBE934B0181C6A29BEED3A3B58EBF112BDB94FCD0F7F95D80B7D76E460D64183D5BB91282F892E0070381F95AEB4F54E49B6BD4ED9D67838EB5F24FE1DF043A67B7ED2324EE84268CEECE84F474696548BDB082AEFE3AA0217B9B173E8CC737742584579628000C6FEA7DA90EA1B8A42DD042BE882C7B9CDF46176B6D25F2E4C68CF9FAC5C057E0358E7F919E04347F4C0334C649E21886E599A8C397C0463FC946E907F6E3B84B86094D53A26A22F7A5758ADF1F8841C56327285A1F7EF63CDBA363F3CEC79A6119F1AC8AF46E1206819EB22F7622376ADB62E02601225BE0F05A43664316CFEF8850D53BF670A4171EA3A5F203D5FAD5958E4BF2F047FBC62414337207926F6606098ECEB2DE480A97DB39D1521C10B421C63B4332668EFF48E9C49DDE0A3BF435857BD58B5E5BE3BD0CDDEC75016EDB7DEE2AFFB43AE55CD0359C6E2E41FA6E473B4A31BF8C060518363A1633A334292EC40BFE76887F470FE2032D8EC83DBC7F11FBBDF71D24C23ABAF737D1E068C79643CDC876B768BCA0CFF22988ADEC340D83778B2C72E0C606D7F145343A30E90BBA6002CD3C281BECF18507EF408BA1C231E7FE3BA3205C061813D75CE3062F7268D77570544763CFB5146E648720EFC6FF1ECE9DBC731D09BFEFC8950435DEEEAFE6A3253C40B8A504617F6F1786A3A9DB2E3DF2A62A7FE7F429172CC1A71A3B05FF7AC2FEA89D89F0365823896190F6F7772A485DFDBB9AB7975766874A5D36669FC3A6CE70880729BF959D1455C228E0D9D92C9C24BFFE4361346E46CF617DC746D5685D8D4ECDE45C0B6B93F1CB4E6263BD0B62646C0C3952E5B22821418196D6D79A93C79C1211825C3EB8CFAFFAFD708512C3AC280B9454CC95B0381E48550A0B82FB675B092DABEE19B5B04052BB9F45448B490EFED211A375FAE6E3E090874E0D318B1E23ED6F9527D54DF1D393768B8B990CDCA88368F132D03136EC2A314BA50EE0ABF0ACB381F7896F1974897DE2A17026A72B11CDB4B9F51CCC9D93EF82739EBD6E5C9992CB6A816B244ABB48747C73F7051C90A72EF136BF582F7E3533537F1230B2552D83A63924C7406F5E26507EB54E53729078828E130FA2A83EDAC54C25A49389960B87818EC79160E06B177B869E3D074B621A095F3553B5422AF1D11E1785A0D048B9D13272E9175754B57389517920721C144D636EE1959E9883E06D1D051644E183C48EC06801A776E12E54715AE4B7543E04C71D7644DB759F08204BA5A7DDFFDC20F4C47213B2AF0953544F23B82FE4C7DC24BC0E98065209A9325677B58E6B2EB77FB6D92427B7DD99225C3CE4F000875FF55733AC2D268F9C472405122F66D141260A570D3BA488A7D2CF79080DC1B685AFAD0E4419C8CFE7162823904F2535E6DE0546231E58E368123AAAF934E2DD560BEDAD34935B6B318EE52A7E9B084D11095357E08E525E47903275664836E920E31947634433A0F8C5BA9ECE2825EA4752A0191F2F691ACD942397982E68C6247B9FA1A989C2C897A65CFEB0A0B1827B63B35CA7A1517085B0EBD3C83DD272EAE3C33193FE3D3AB4D1E07F0447128BC1F9D545367E639405ABCBB902D72623D4C6158BEF4304F62B43772DC646CD9EF9CFBB7F221778BD5FCAC281D2FB86A3DC4AAAB1B2629DD8268A9117D2D7911DAB67535D417FA4F457123A3FF7C19AEB95A051214EE18C87239B3B82922F86349E4210A59B0BF96A089604F0A3E7B3C9668185FCB94889366B9B36AAAF934AB305F4F8B69AF39026BD79A57B492D55C1D1FF8C41A3918076FE81B90E3731254596F64D03187917FFF425B142F9DBBFA6C2AE6D6D6C343BEF7214978F7FBC8D112A8595CBFD8040688F304D82130D8F65D396192239EAEB2BEA497A75F889B0999B2DDA23F56B013C7D23893987CFDCCA37E781416F63F8BBEB28C0820CFAE1255141ACAF17E29F314BFD6005E29A322A46D614D2C279BFB688456D028E084991553D2D607368C54ECC14DEC228A1C682C5A450958D28349DA5D024CB9DCF0B41DE0114B10CCC4367857672C86C13059D9318113222D3B4FEB0E2D4A95FA850A3918E0B4E17C81442210140C09F569D3A855C67170C3CBE96F363D9A08FC573DA8D93883F4F270DA5911B8A5C2B6B9DD677CE6E7744DD0E91BB62226B4F8558D06A244969C4694F5AEE49C202C28D33C737DE079713A55EB4FE792F8718F13879129B8BA14EF85F1A97A6AC7A5BB105CCB6E8612B036F3B5F6BD0FB298818F76D90204734A422BE53854DC53F951868E3326A0777BEC4A77594633026BE0BF46FBBE046FDF438968157E95A3B9454BE725AEA44DCB35651882F44AA821E7CB512B2D4D14FD3919A288035EB139501747085527616A8F40F0FAA6B0A4837943E2138BC682A14804666D069633CAB49111A1888BC9AF37E95515F4734B18D119DB48D07431E2D9D49F66FDEACDE44B55407DBE2AA06FE1993BA41ADEF17E1F04812BACB43B7483B932603FC9D8E3325407227232BAC2D4882B68EF5D7882DA8120DBF2C65234DA4685C1005351E6B0993BE819699BF4EFB7B72BDF23C44106B207DEE25BE89F05E4161C828925E39B55DF20E95FC9A23E9F8F6FE6EB98548E3A74C5B09EF86B638D6DB6D8B0183D71595F23687A5FDAEF27E6EAFE109AEC416A7C50DBDFC9E08F1E2033E18CEFFFE3EE18E6AE71D0975983D50BCC9072FA049591C7AB653EC9198C5B4F2724ACC278BA80D26644CAEF85688ABE8D031F0A44A844668BA1F919FB86C577A406F4F4DF3953287238256E3C106D59F701313F79D9E44259A22B002325FA8B7A3105478C373D10236A1FBB1C703D01826585CB925F69450C825B91E0E7D815DE41F411426F18D873DF80052244C6BA4DCED9641FA0F5A8E93A9C84A1BEB6AE11092470CEED4CDB77504CBCFB422FDE56EAEBACB599BD73977FBD7DC1EEF692DC6510BCC0D673484E35E13871E2A8EA47F171C1DAC808276AF1597EAFF6F8ECD89DA10A3E2A3FC6BC2774B3780AD3EDFEB0B952028CB198B18EF2B209FA98B4CCCFCF559BFFBC2E473776D1AD3CA98C40590823E851E114774A565EC690C293211DD869F1AC50B8F4349D9422CF013BD89FF8373D1F39C3F97A6367B116135E16BCB8B57948DB100F441D05762DF0040CD52F4E8C78BBE39E69B502CB99946C4F03A16D1130CB08F378725C10A38461BAF72AD1C708061CC192081886E87CB5A52B532B3E5A0800D02C0F4ECE2BCF300438F96DDEAC6BCAD50A7E3C07682B3772849BF7752A81D36C8AEF83714265210EB6309277C6B866249A8B28232795E05AC93289E65054E096663619CFB208BD742A8CD043BC3BF699D86448589C680493ABA11FF063038C84D6FDAE68B8847C6B35A05C054EEECC4619D7B2EFABDC05C604DA9CD07CAF10C39F05DB6E536BEBF1133F9023751120BDB1720E72771DD68D8000F0424C3B8A047A806433662FF54074391F230239706F27DC7010EB95D1A378121276EFB4F80A33617439F55A1D5A63803AC05C3D60DF8B79EA7C36EDDC12772780CB3637AFFCF391CD154C670590296455B68BB9B90877D25FE6A1C46AD5EEC7B7DA2DABBF5DFB85D7DA34E679CA2C08B60C2F00996CBEA3059D4DDA01880A99BC41CB1A340DA8CE3D5F8C4EAEA6696AD4ADEBE8CA748E082D5A718221CDBBD2058A8EE944A0AACC0855DD06C97A79A293BCE45998100D8F605C023EF62208230511EA3A8F7DCCCE835822E687DDCA1853693A82F3D1C3F090EBDE6E5384C680384ADC2122BD517AAD6D385E637219188D9F11E47EEB948F16AF692DD809B1CDD7B20ACCCAA20E6D1F4FD5692A2D09494231705DF5AA1AD5BF2BA19845FAD91A3096ED81E7ED859D31A7BA329EC5ADCD1F58F89AB2D0963D67569670545668A579FE44B9E1621ADD5C9378B7AFC2F96716FA19220EC93A88C2D551DFC15EBB8BCB4B2B2D491DFDA4E0EBDEA9055A76548E90DA126550F147D8D81BCD265BF10D6DCC0F0893C87ABBDFD545C5B7B6ED2EAB2483650892E011D40EA39202AE8975457ABE4B30E889C8D4D2291DE341976075A3D589A089058B57A661DA35D4E0ADBBF4A98C9107A6ECB8EB85EB71C8581ADEB27B170AA519CD3DD2950B33955EAD57FF7DD08761135EF6DD12251BF92B8EECC59F97ECE985174FF6A81B0F850F927DC8D09DD3FF982C902D1A09D3FEB3622E92EAE2BB9D2ABBB7924EDA2A6E980033D2509D8634917308B6438EAFBCE06F48536DB1D25CB015C65D895F7967E3333ACA04C431031C2E952853BCF922D2A2795E18E2993CE6B123BA928404713D82964FC814B6AD6999FD313F225CC6C404D72FF29522A8FBF5801A3478F99D0648336124179BF91D5D8013D881AB41BD2988EA0E49B2E972A2DEC6DCEE4F3A5553C388E447235F71F2D157C84BCC0AA4EDCDA855057C24BDB1E144C7F6BBAFF79A3DE357E3AF462664A81F9BC503022573F9BCA397AEADD23D6D8F3D312481523F42ADE0BD764B3DA1D6A8BAA21EAFF1CA37228F600F0111A6E885107284B373ED361FC80CF0E6DADE3DC9561331559316FE4E23F7E54E0547131ABD886CE17A8B1B42A8EEA20F6F834D523CE45A069F86A2DB50C279DEBF2965F880BA0D84DBF07A2F66BA4CAB6381C01E4F9AD49587BDF4DEF24590C213D4A6554352FD5815BF24D11A735EEC099FC5EA7828C33C5890BCCA196011E2AC5B65A35F2FE4C361E520DC326A8CD49DA285D634242FB78B2C1CD634DB0F104E510D82852D5494F20CF91B1D890116A535D7E5A31850F648E80FDEDA46E830645321A169DFE34575F1071F6692984DB528FDB9745E5D165E9903E3A548B75EE8F1CA74B63A757EB21A8C33C99491AE0851EF9AE859C53A22B7F182F1DD01AF9645FBB7D0AB06DC14AD717731DFBFEF754004ADA3C8DDD2B15B19C83466D51A2277915D3E112811A36CF1BE8105F592C6EB7D86CA9FC56F0700E13CC3C6B125E8BC7E0DD5552AD4CA81531D7187391BAB9D9EB2B1D4BA7DCA702ACCEC91247C981BC28AF71325C1F1621FB7ED3E786FE2947DFDF56FDA8062DD81A9FB49F386575A1BCA60CC35A8AEC0B27A4CF3E8A57631D03E75F581B8028CBD830D2EFD90B2DE408644B01355D07A20FC7128FE08EFB41B3D1174715C485B5416599E0BEE7B96BEC9828E5D1A1A37D10F2387229E92C9C94B9D3AFAACF86531802B72BE362B9CFA378D752E55A6500602EE54B6B925D8EBAC97B5C032C0EE302911EBFD1F0C9F96485B0D4C20606DD1E5D909EB898124CBE9F9F59CCB7C0186337AFCAB364174E9D85BCE1A12E9D0B8608412E9C1E36268002B0F6EFA78A9DB3535CE4C67C4658B87F3DF348581BB21ED73C90CAE966457A527B0A699E8A762C8ECA17F75981D1B92C6F5D907E856921DFE5ED59370D8DB709E58FDA1C84CAB859D2BB8987B9F6BFA2B9678AF879A1D54EEF7DC7250C3DC4D6FE4C16ADE97D1CAD6C4F2AFEB8A2010F29A3FB8D488A8FD3EF6E07B6994BD6BE0281AC64A690B12594C854892BFACE17941E4F8FB53CE7B7803AFF0BC027E7CA6C43544057F33EB84071F53B5C707F8CFB8FC7ECFDA740B8241F9AC8DB2C2D8665B85E0578940FBDE90A07188C24F848A970EFE1B29E40B69C2C5AB11A2FDEA9DD0E9D234E14647E2ACC55A4732D1C15B7CB014C20EAEE4A46A242D22E5645E0E1A71CB2DA037C69177A450870A9B7641DCCDB2543A1A82FC77522D4FC2B58755A621D43D6AB676A70C9063632A8BD02C1BC404808123E883EF06D5C929C8AC71DB21B3D60DE974349CF5CFCFDE47CDD035DAC85DFB71EA504E77B249120E3F0EB199CC360863710350E076F12D7C2EB7A374DCEE91B159A8C658867D3AF3FD91EB71C9A137F81E54C58D94ABA2978440F7435BABA3DDC081CA9BE505BF96862C6680C487F24DE49A964C91AFB7ED42725E4C103C2626FA85B529856064A9479732A1D7766F5E88D6664B656A3BE1AA674429E0613CCFBBD5AE61D2F4F8A442FA678F58CFD57A187531A47017915767C997E7141986C1486E14035128F7DA92DE3E00508F1D78F3D73FCCD31068C5F518BA3EFB0CBBB8BA3CFF43935930DB7E73185CAFBED82A80D4BCD8D49E1D2B33BD4E0C1CE7E9EFDFEA0A4C8EB10DD94BD51B690BCFCB017ECE3AC73D0097AB44656B0307FE59B7EE626891D1064C29BB779600AC190192DBF462B57E8920366ED252BFF0FF406A6F72D5DA29716CDDC81D3318F0D8E440DD4AD4A5895DA7C8F8C46FF651603AD2B016761663DD7EA0B758A3DC019F3136F9C0385BDEB7875CA5DCA95C74B13F5FBE9CDF4C373142A5B7D348F4AA58FE830F84CAC5CB1A08F9A2E2BF9C1FB2C624D27CADA4599AA5FC861CE933116BEF79B2B73A1CF030F21B04C1BE2CBE2F0AFAEB6D172F255BBC1F04A511B9D00ED74A66DA84B0F1345A4C874ECA97088EC76E717F2E83F50E7E8710F72356B9AD9EC53613C4FE6052CAA6D998DCF470D957E50B94DDC8985FBA9B5D81665CAA6FD5027F0496B2095502442909C4032A477E4CA0CDACBE3EAB381562ADDD43CDD61D4228CF613397EEB81B8202A2B2CDD5C1C5A4645BBC5BD1CCD7386CD382E86D7CD3228F6687145BDA56D3FE2F3E538B9887B5EAEACBC33EC79F6EE5CB92617C4BD7C8A1D8A709EA64C05D9387A3C4D95F36F4042DC87DBBCD9910E45C391C6F4D13A23C47932634C784CE7A96F631015AEFC4C9C4FA8CD06B6442D51A612D270E5BD322E8CDF4B7CC7AE4482AEA324CF3AFC10963C78077627761E3421D14620D95C8953FCAC8E485EE05FA653955DFF93C6160F242108F83065E0BF1367847DF7D0D7300FD5A397D99C37AEFF6905894E19FCDF9F6C75AA07171F0215028D265E5337A37630EEAB770A61D5F9E8F8D5E8E3B46211215B9E246096730E57D75A1357F97ACC365EA96DDEB75FE7EB6B39393565CFB417FCCF5223D0F981AB48466DC16759B02458F242DD0DDD4A357048AF6A5E82F7C173324E84C1952A078AC85BDA36C05E713D340DF854716D7618F712879B573FB8B68FD2E47F8846EBB1C3D66069DF055D92F9CFFD6345197D7A9BD0F7B76BE7CE5021512550BE79F3B2D2878F6B884643BAFF933EDF1CF8CC4897725BFE4A9C84F721964CEB1D56F40BAFD9A0EAE122EA0076622A4464907C4C384D5310F276077C3B77277B9F6ECA541FDC5F1682C57751BA38C14DFFB4D28959FD25391AF503264708B385C311CF538733B18363A7292DE553A9BBC41DADED8B7B4D0A213EC6A748EACC37CBDDBB91F4A67B025591E16845708260344B8B95E01FB93019D3DFFF742BE6D6CB642C870964F0D47DC3A794B38C635C25A413DE6E14CC5B4C5F6A19A1EDB9889F57FA8BA3B4025AD2658007C5BE0E11CCD43DE6AF9F750473E7178FFD9C2A8C212B2AF3CB2AECB2605ABD981853EE019FAE50748B504B40F8BE10339D97F9CD901FAA583E7446CD60657E9ADA31739799D2E07223001F2EB7D96CCD4FEA537FC8630D7874CCD1E4DFC1B763249D452DC959A4B953380F8A481CB2151DABB2973ABD3AF676096E4B1DAE64700F04DF7E80F765E927ECAB9745C7214EF55D65BD6B1D31A00ACDF3F81404BF32244E99ED5554208AD9C6952568C7ED96762123E0FA0E5E02C82C6CF5463891D4DBADBC80A8311F77F07BEC4DBCB71F875B373A8F46E4DA0DAD38622D7CDF2842D621CC1B8A6CFAA85C5427E52B4B3145151E203DAD62399CB266343C396D6FD913661DBD599862572A53C1C8BCF412BE600649C991157AE3288933819F70D6497E10670EC18EE8E07E58E5CE3E686DD5602AAFEFADE329E44F0938C7961E95DAFAA7935891251A62F32F0237E920A23B4A458B7F7932F144FC0CFFCA01F1B5345CB9F9552FBE69FC87E083302C6F0E153693A0715D0BDA2BBBCCF0A48D2C5D586F72B171938B880DDDBBB13DD970D6B475DD0CB8640D27F823BA44272149A7B70E8509B6B574D7213E9EE767AFB9C2242D6B70D0728F3FCEB1EFEA46BB1A1671E4426DD1EEE1F479265EBB930838CC38183988235C58EF083097D572910C70F4EF45DC85D7BDD9BBF179A1991E7B9C6ED5C250C19C0C95E2C8AC592B91178549EBCDC6457337398EE90EBE8EF74AE489C0F292BC7E2E5C069287C009CA074C830558EB7C28B88D5D9DA8173EEF59758276B7754E94E588220211ED6DAF4FACB479F08F072860E8FD3847C3528941DFDD3DF243859B273AD437F46B8E0BDA4995F4A8F4E0213BE6C01D14AF030BD348A497588C2FBDA403DB2C2128D95A694002B8D8BB99BD76E8D9A83ACFA8EA10901C8ED8EABA13E6AB987EB492BEB24F1DE90AD09CB3512046A6616DE5105E421A884D2EF6ABE8781196C9E1B048928AB2B36A8EC3218CB989A5A011F0D5037E91B742FE2D858F938A3A061A72A8D57A15D291142B56AC7100F092028C3A0A9D001A5115C18803FDB3BBED5F9BC50AA8BA6E8AF302DCF5606C319058BA33915F81D2AB16829AC833E6FE8B9ACE289D5E6B9C4256835A212BCC099D058077896790372F17FB249E25C6B15F6AE876FA31A0493D926EEC95430C4B16CF124E013080369D91C5F7E6A1B1AD08B6100837F5E0295E97715DE054B902EFE0A04D034DC8787709833C0B0364DBA5FCFBEA92D2A18AFDFE989AAE00EC9AF6C2D37D23729F55E93BACA9CD3539EA3B4C8B011B322477AC1DA54D0F4042FA674D944373171FB8047F7847DE44E714083E9DC850FA0D7BD8BDBCE5FA4E0BDC356F61146042CCA3809B424C70C6821601D218CC61B3F80943297BA9DC412B26D3B2CB76E5436EE63186DCF78402E8B7CD7E310BDB5E2FCF9AA6472E0E3933E8561AE5A7EBE9EA4DD14F720588F69763B8B3417643F1EE536DE7FA9678ECA1B0BCD7685E5EA26C07D38628FF7CA45CED0EF8004B41E6461E1EA61C2B3F20DA8E33272E8DBBE09A7374427301CF432B77B6" +} \ No newline at end of file diff --git a/slhdsa/testdata/SLHDSA128FastSHA2_2.json b/slhdsa/testdata/SLHDSA128FastSHA2_2.json new file mode 100644 index 0000000..ab58170 --- /dev/null +++ b/slhdsa/testdata/SLHDSA128FastSHA2_2.json @@ -0,0 +1,9 @@ +{ + "parameterSet": "SLH-DSA-SHA2-128f", + "sk": "F9FFBDE3D5FB47C2669823076777A941B810BE27FEF66DD4353B4A21DC972842EA5EE0F3E9B301B5CC32814C8AC6CDBD8BE5D429B0104F2F0A929099F687A74C", + "pk": "EA5EE0F3E9B301B5CC32814C8AC6CDBD8BE5D429B0104F2F0A929099F687A74C", + "additionalRandomness": "FD3FE9ADC298FAD0BF5A08D804C690B1", + "message": "3039FF4B78F80DCDCEF91B8602456D99621B7736D6207A60B9EDF5C662587DA6B2189FC463DE94448055CD3C65C877325F1EA0B77737A8CFFD63290D3D8E229B35804F7A78CDB703D2EE6E7EF078C3E2FA325585F3B326019317911BE16633EFD4BDB429AB7265B4A4936A9E393BE78F7E0B74707DF758EAAAFAECDD815C973C8DA8BAD70842EE6EA3D27D4C9DD1FC7FF5137779A3C0978E95BD6B99E0088721DF086F35BBDCA3F68417301585EEA71594CDE5097D7744FCDE75B4AC387386D4E8023A45E6904DDA1CF4F0E7BC75034969A30C67DC6F231A4D56C608B7BFEB75FFED8B893811B9A134C178A1919252A47E6B6B23E885C1738C7257A031829C9596827A0F1A81B5D5C7F5EC9EE3B08CA5F6F88A545581F779FDA50BEDE7819621605C511E37489D08BFC1A4E0FFBF1997603E21C8A3FCF1E7A0B158B78A75976CD5EB5DFC47EDE317C02E514768982B555D96637EED6B0DBD58990B1B605D3711080D782A14762DC4BC0F33B1BA4A80ACCDB915C2869F7A4E16717375B1A027F4DF0B92E944F80E7258EA1E68B9232FCC0EBCBB8851171C9AF7BD2323E986AEDA77EDD4E9178F68884167D9A33A710B39955DFC607B9EE2B4F5A978AD814FDA328D59D9C55DC4290B1D362633CB11446F6AA473C3B93C3350B231AE409A85BF679BA5B47FE175DD0820B69DAFEF94E6A3EA133340B775D842D8BB3A3BCD4AC1BAE516C8B2B52E839DE52B39927F0C0CED6D7C8B5C3FA57D645769AD6413C3335A481CBBFB82531F756F73E635D930AC851F4EFE01A40AD6A1114B05D4A3F210CB6013C22B922B3C6B7D306616468FAF5D3E5156248E946D0B474D27BFBE9DA5D5892CC5898DEAD776728C9612151EEE380AC47249C79AF8259C501669A2AEF714427B3435C8BA21B09A1EBE86BA0A5B5EB7C96DB6B1E750BE628D44AF9B72953012501C717CA602787A243018585AB3E100B1DC37DF0A645EECB0DBCA339CD4CB3716E06DEB5C0F93014DC116463D6E3E58784C703ECD47F3BE07CD8C3A03C63B3A7756C6980B594C221A0427FE2366BA3C15CEF9D80F428DDD797945A932AE0674BF3AACF7AB5426722480815A256D374D76BEE80A3AFB75234D72ED32CB71A29B5F4C6B5BB330FBFED834D38466DA5067AB111AD54A65C02B966028A652FFA6F805E73575D4E35E9194326FFB260B83751940317269A88395C1403E11C28F31482FF98FE95069A7DC83847DCDFA826BD571EA368347D6F194D186DE7CBFE11511006A7D92E0F5604FC378485B3C551BA0BCFEF3D8A131B2AD8B70DFB1B96B55CCDFBA23162228D365C3D7B5FA837D05B13797E715AEF5CC2380A0BAB45B23277973CCD942065237A314A1CE6D55334D46243AD5E0E4C898B8FC29695ACCE80791440B2DD1C429EB0C77CCDE47AEC84CE9FA879C62B56B85A09E9D6BF25FAF70F2E97C35A21C48CDC09D8A4C69693755681BC8093CDAF00E7AAD4A1991EA8996C336477E043068B204C37753C3E632EBB72F557BF673A6396781660ACF4C9137F14D329C54EF84BE96BE7DE214588FE1B8196523227AE4F6D39C6D89EEFE7181A68BAB52C7EB051D5278F5DA6979D6D40A550940CD38C39CA0F9885A0CD7368C99E65CEA2C9936C259335CE51784F05BF67A60248B0B680376D8ECFEAE5F51AE5D0C5F59D4CD4F6B7A944B363B39758CF836B95E869399F94030B87C04B157A56A971F8C10E892B0D9E7B82404A8342E54F1AF0504BB467D997041667D285233B950DF6F68CA7DA73BBD1ACD11326A829735A457E410F5490787568E8FCD6DF3B4CA576E2BCB63F9489316F9054E05F0FED134EA1B07FD11BF0A3BCBC3C63B51B71F1B16236853CC20C05FE221B45343531954CA1F2513D0E07AD77E9883B9A87537AE5891590A539C54102FD8DE8B9689D34F7C6EB1896578AD6F2DBCF02030EF37C0AD4BDC16A396C27BA98B8B7E50A7936FB8B4A8DF36E5F935A8FCA01518FE9BBA562F332AF88C04254DE76F5FAD566CCDD1081FCCA98DEFE076D4A3A7BB106319EA38D4BD756577D880526C26C56F0A4CA45C267B1785F32AECA8906F648477BA0D35CEB7A21029F88436519DB7DC880644082C33CA714EB8F077CE9E5EFB0ED2EF24024E764E54D08FF0ED653EE3A1BA47EE6B735667DE10844405F7E1B9E8719C90805996D0F5AF7B829BA981D2FDF66EAF58466CAA62707E991F6BFED0532B996E383CD9106A9F35D81DB9D85CA9A3B004C735E482DFD4CC5A6901702B88993C73062B76AEFA36E43DC84C7F2ECBE89A8F9686698F9F9F99AEEAD4464FDDFE259CE8A4444433478A31C483217982B62FB0504D29AD3624174B6FA9F58567A9C1EAC601E5FA98EBDDC0339FB4B7EA0957DF59AEF99EE9EB9ECD9EDD301007BED0793B0C82DA95885E4AEC1298D4F2B628A0B9FD433823FCF749DC7B83F636C9D2CC74A66D13A910DC9DE61C9BE0347A4822D466D9DA8A451FA81F2CEA7226456687F2CC4F3BD8FC0692D7CE7D42A51675C6EDA93B9C50C598C3E1460FB0A3B46B645F40F7A94136CB1003299F230801EF2FDC8E2692A9C5C7E9769071A40C0ABC77F1864494465255143CE56218AF0F5453EA1CBA5F2B5CEAA3D53DFB964A07F565414F325C55DA4B97ED51EF94699C277227BCAC026FFEC623E3996E91E942662B79CBB4D6817B0F971E2A5DF180CE3D43CEE5D9B0336F30F625B3D8657169C14B436F62A6E0F558006DDF71A0F4EBD7BCFEE0FBAB8EA99ADB7295CCD97C7D768FCA52DD578A3C24C991675ACD4AA38BF18D55E2E8F98B9D20C7ECE204BFE968C7ADBC4380DD6411E0C8F4372CCA61C855998BC2E544244B0DD2BABC9C1DECBEC893242F13F8257C503A270C459970DB8388FFF17DB1FCECDCCF3BA65AB54C08D539D19CA5E551558B0874E131001B49781471E825C2F79930CA14644DB085C51C580C60E2E9F0F57125B3744B2DB6F0348856DF7A6960E19AB48695DAC27A3E7DC00FE7F04FD2CFF3EB5286A3914DB760EE26C7EE02852C40FDB0B5B9E4E1C3AB570699E416DF5B5045E1FF95A1DE4A693F7ABB8625958359F3FECFAA2214D5EBE82782DBB6F7DAAAADED8486DE203ECD5AD7CEB9CCABD2D9E24FDCF18A083DC967DEB5DFACA5400E338831F76F65A9D0EF5AB260DE3DCA9D062E80F7050E33DEE5F3EC88F02E270D37ACF79749D9086347E1FFBEE622BD5466A60ED3B378BA2B207BD178C9F1B2EF1899829B91314315023370C039535AB66BB579A21E3FA7BA4889C88DEE0B97B8A2391140D82909E0482C125A3303B4E808C08360A954282ADA5BC59B118C42158EA4C4EE3C6276EF1523F6476982160D24DA193120255C66274EA34F181E033F747CDD4911D8C222F1A38605BB50787EABDDB915CBB6916CEDD5C7FF8568929B29B298E201C1EBDDEFE5E8B292493CEE4D28B8ABBA2CF10C5EDF0BFDB6B207DA55184FF73FC6A764897341BCC3F04BF8D37DB18C43E8B478A9D7A8A825745130A2ABA2DE420F6CFFB5FB946EA59BE40787307108E2AA86F8F69207ACC55C5F1DE1E657A7F8D32944859436C9E1AA0BB1846E0585923317FA539AB16B54F293ECC57619B04C68758CB5033BFAA4D993A7FD5B9F59482593FA97A30F6D5FDF48C18FDBA27552090C23C74EAA252EAF9E6208114A9DB422C767EB6BB9588B3DDB602969A9423C9CB848F8537C108D7F6651FC9AAA3A3F722B391DDE110B510576435E37A594009AFDB34B8A0FDF5BE546FD770B5E01813997CFFEAADD394F04D20832D8F6D37CDD5C5B9C2B74D74129E3FB186010240EA81B540B22725DF699DEC06151083FE15F5E872F6CA99C8EA3F17107D3D32EC5335A3A17D733DB648BC845C8037A339AEACFEB8FD55769E3332C3FFDD7E9BEDFA7C4DB85A32AB04BED5C87D68CD0BAE2EE48F091B2D5890FDFDD41877FC8C1139ED298BE5C21E3DB619E645F6749C074B3BEC599FC5CB1408238A6860511EA52BCEF1F4D456EA0B4E1ECEE64160C4886644698691A7E02BD20DE2CFDD5C4C84F027A6BB0CF25BB4C27E017FF1AD11EAF367C4ACD61ECCC289FEE7200BAC894F9EC083634BB7F02AF14FA55E7A6A2AC92DFE7C56ABEB45838AF272FD2D4D0467DC7BEA1C664FD96FCE60982B9257C50CFC146D101455BB78DCF9A32527BE9B896B47B63F6726DD9B0E90BC5754228D3E27AABBB8C67FAF82BB414343EAE8007AC7EC4E1F85ECA7B7DD238FC3E876CA103D8E0D28395365B9574C2C1EDEAACE9E5494DE2E42CACC24D2E51C3C2906DED76011FEA976551F960A80DA42117A9CE95FB8DAB75E2A322C37738D48F9703A318E3D897B942E472F5C2C4874E6F3A8502E15AC63485D14F99D82D7CD746BB25305C145B9DACAFF0C2C13146313CFBF0F4101A4ABD33002D3F76446933A7342609F6C44A602E951D2C17FA35B311C5DD4160230B6DC41BF4A7748A33CADC08BCF6E458F6193D51171C880FD828A7A1CAA90E453B632A0B61EDDD87B145B2A53BF022DD55DB6EEB879C8736782608D22E26D0E57017F9DFD439336505C4ED9E530035E1FAD6C89A8E7E5D3543FE459552E34C9FE405A671D21CB41D75FC69E4D1CA35BEBB5BB57BC5B5EA1689CBFC8857A9626EA9BE6988BEF13CAF528D4F7894E6003F879C3FB452F55E4E8D65630FCC06AAC575FECBD20ABC5AFB0F07702303777BEDDAC88E016FD76D7491734519ED4CB6C3B052F010D3DB08169EAE8CBF69EFC1D37E505B83C95B17650E9FAE13D3F9A99BC6DA12C54BF93D364C585D863D7C133F86181297E87D9EC3173B091112546FFC35C93B7D2E48EC90E74401681E2FA118B0C06742F5DF650258410E378A257A4DC07CB6DF94C4FFEE5A07764D80F604257394378DA23078EBA8D0412E3747985E91815E225515ADE84D09726DCB0DD39A6F457F23840CCE1F9F0BF441AC4DF8CD75425A5390736BF3B8BC99704F17F79D12EE7C30140EA1ECD5EF96C00865A4E77AC184C2AAE588EB76933125F8A40F35CBFDA75134738FF9050539195FE186EAE191155F4DC39AB93FB1A3A308A6F20E08F6A28F4F86789021D185459FC89C1A373ACBEAB3182B6E8DF7D7AD03B0CB8B4BD8E1FBFCBE24AD7CC877783128B1175EA753D3313DE7EAF19E4E7434F558984AB76D50DFFBC18B0EFD279B021D0CDB6B0CA35C481642B929C0F7BE4932ABA46379963E031C2302039CE973F611AAE95C97A6DF99BC11E1F5716D03FDC527F8701EACAC07697312EB3C1859F7372B97C3F583702A1BC210269EA7762F0F3E1BF6E1AB21A86EDEE008D1FB5CCC377F9DE89D123CE11DD91C40551BE6497BE4E798E49176D51334D631648E033703654184F1F213F6D60087578035DD440F4496D5335F3178038FDAB10912E1A4309D753360F58E11B91AF7160A7964EEE5DF44463234D57898A1798003CD11CE1692D1E523C324538BC90A08D7CA1A7D0FDE7E1AE1F837018299F6844919DAC422CFE125F673CC64FA253E6A3657118F26C399DE91339DF8B7C759F0281E79C9334060E95A4140A8BC104E7EF0EEC9DB891EA3C2E30D0482B83B66371C897548A32F5800339BADCA867DEC96B656331DFF75799D671CF98B409384070BC726A8A704D4240BC85102BB0BDFDC9945163D4371FE42311C4D785363C9849E69D5665985DCCC94778EFDB8ABB9B4954CEF0A182371E12D7F2E83D1699B9AC609AE75C07529884F2E61B5522CC2609F1FE1EFFD8F8E4CDF96DB830E2BFBF52CE1297A0B59D4773BEC99A670507709033CAFCD5EC77BF084BACE94ED6575E9D5D105CA35C59D4EEC40B679801206AD2AC8E88415D0C632AB5F40A9A7C1B7F2661AB977FEA2D1ECC0C49E24F9274FC4637655A457DB038F48046C07B15E138B64B0A0D0D76A23806D405F195F0F361766F207BB4C1CA206EA91E122223F2C20DEFE34303EB6B76A31A2C1FAE2EE202A268E46DE7EF18999E0DE868FD455222E3B420780395CA6ACD186ACD5B582E57120B677456E6863E67E6D8CE46288F0DE78767DC75F41E09DD4B446E2D708066DC3E917C5339BA5B80A67C119FA7A61176884DEBCF0A95A387BAAE013206D4D4086875FC3A0596256326D71BA795B48B4C8674F25488DF423ADB71E96F1D8267B223B75422F297FB7AA2749E5CC07EF9A40D2EC46E86F7BFCCA8EED1F100D7D7C523DC268F68987B4AE3795C489BEB29AD62FA36E5478B9E4A46BCBB6DD2DEF40EBD2A37EA2C6C9E44140BFA1541F679545CA4C004D694FF6BC642FBF6DC8F56ED9E9E4F154F765232FF1D8A36F7649D32A397ED29BC929D0C4F87607E9F8D519E42F189DF392CEC49DE8C9773913EF336C18B843E1DA9DF187D78323BC020BF86812B65EAAB5ECE27C60AC39BBB1A8471C330AF5F13E2B9B8EC252DF754B254A228428E25A0B67760EA9810D752BB4DAB6F86FE14C2D7D90F41169112CFA8CFD6E22656F8B5F408B1457236ECE20ADF8D37FD90149936C87F3B3419B967CCD3218502E2323875990FAB874B9B5714435AC008DFA2CA44E9C4589F6E5EF3B438910D8E013565AF8607906A6972FB210EA9830D6DA05DE0AF863847D1CE8D396A150A46CC65CDD6683DA93D9379DB121C3A5DFB886DC932E7D9F3CF1BCDDD2F4523D43495075B6CA51B6B36D345CAA3455415AEEDC25D4E599F5E17C262E1D3648C735E1D107621E94BCDE5AC22D235F1C6320DA141ABB871F1550B3934A862F11759B57BBC697751B6F0998ADF3FBEAA875C7AD5D2EACE31FF0A7697C7E8D9D548B4EBD17CCD4E3107E08C1FEDB78C77ECC3121E8DBBA5F33B32490A9A8F32430077325BB79B7DA97AF89188EE097438051116FA2BDA5C8B10937CFA66C93999696A1951FF819903CD4DF8DA46C696A82CAFC3D428ABBD19D931ED40AF3AE763D2D85F74B190E7C8F53F95E8F425BCFDE42C255E2D0C71C8EECAF2D75A8E436326FD0003C47AC4520DE0D09A649BE4CC0937DEF29E1E50D2DC13FF85F2F5B9C5D9FA84E8F1C635B31EC36C7DDF5DDCDE40E5CD04542D6DABD5F1C9FA3DE3256A6EBFF8317A25B1D64C231FFBBA26C4A61E2D6918501885D23BFF0EA3AC71D62ECF16A4A403EB0BAA0E2D2CD9FF99AAE4A5767C1401E4811363FC2C85DFB003690D0F95550B7A1C8A316B0CE12BF9CE6884EE63AFDAE32E647BB01E20754D050B0E48D3BE022FC3091646F720590CC3821CA1C2E4CB11B1D0CDA784CE7332D1289B55EDFF7BEB1A57C32EB18CB3EC0C008E91635675A060FC484FA91C878637D7D3AB1C550D52137FF848852C0DFA1066B40C54BB1AB4D3A7BAFB419F6B0290485ABF86D442C01CD34C7E0B49482BE799EF042F17CE82975A7F6F251A256FFDF381CD47E5840F6AE324134DB21F5306D005770F59062205A2A50AD3BDA340F68A301A9C5EFBED00EE6D2549D4353C65531CC85B2BFA2E1E2E1C43D294EDC8187DE772FE84A613A860930987245021E677AFB175BBBEEE0D2E9F4272945DA3E7E4599A062BB4151482841A0A075CBD708EE83038C5BC6217FD9EA299BAA62C8A9D00D3D1C0B0DEA7D887321DC6DBABD1390D18C0D840E8C83B6AC6FA6BFD9BB491D0D5A32DE67DA7A90879BE849D72B4991E87566ACE5B72B401C83E6887B05245B6BB398034EFB053838F33B39CD9F3992F821C4FE17B5D1A65E80DA6D12B384C3D5B5E810BD41B2AC747D61B75E89C1F6F0F93B00C8FA1054C4564501A879B59AAB99C38E962A8B34218DF8270996FCA48282E6C84E2F5D99447E3B4975D43A6CA56B4D5A2E4379D7C1892F269354D2FF9FBBD6E2E65B05F8F4F1D20F024DBE51B38C7CDC969C605865CD245328518B168E3D973426EEF82247E3F68E9CDFA4E6DD58771E6B613D36EFFDD1DE7E4DDCEB806EEEA6339BA92BAAE83DD3D34F5536ACA2A0043D0B857DE4CACFD019A30A85B6CADEF21651887E5CB52AF0DE2101BFCDEA8288D292B55E6004F7BC1906E0D5D81CF4D40F23212E2A7297F26AE581DC87E9D7FA79E2A1AB9EED15377FB3F0F4A794D560FD88A8A5E5FB6B733E544C763C776F0AFB886AAE7CF25DB8C8072A95E6D546B65BBE53F6F4D282E3901D9DFCA6DD3EA949D7CAEE4B311E517F9D7768DFFC91DB51F2537B21B8E2C576B5DF9A43DC553F6F099277B197A8AF3975AED697F99970B8E0D9FB1DADB3DB4F7B86F4E158C62D730F61786D49BC2AED7129D5933141FCA9C1F30A0AC84FD082FFC16EC29621624A4107D9F54332ABEAE4C46C7E35DA10EA488A02BB116F7AE856BB9FF5A70EB1DEA70A6E412B4534C3AE1B794337273E9EB615D148FF0954AF49043A78F559065D07A4A57379C7FF1AD302C4651FC4EC9134954FAD6CB9824C7EE7092B5F07C57381980824B8723406C33E028256D297522F9A80BBCD87F60FC85EE5ADCDC359D5841A38B6171C2CB4501C3463C94BAA6394D6EEB8DD4A8EB94F1F40E5268422F8A1CBCE00362C291E535D055B40452F09430B32F0BBFAA1521B6FE2403A8011567775A8F855EE6FC7C6AAB37E9DA92931607A6637DB996E4AC423EEA5F3F0C807DCA3046A768F84AA430D89294466760A15D5310288EDF97CDF1F88D998B2D550D3F8E413B33E07917DEE25D5154D694DFC8658C8419CFFC0B6B8A87D20019869E39F77AEB372906E9D729F88AEB034889DA9DA301BB8F04BA6988644ACA4041C76D4854AD606F6D7B7748031E7A6593DFC5522251E0F3D69EA4DB5A94A48E31EA4862E07ED30DDC0CEF93F5921DA995C41D08C8FC3A8A3A5DC737BB3C7C4D0F7CEA10C80039A0E89E4E6CDC1B419DC65007C6E1A3FB1C0B10732A2FBBAC2A69CBAF472FD4099D8B0EBCB41835AABD77E6E35756D608BEC409F4D1ADB4BF9552569276C8C984B9A76E9CFA62BF260FE220EEDF50FE70FC327810A49DB0FC27F91F733E358CDE56C64445D7C24B26D4BE8282A9ECA8B4C594D80397204AFDFD366F358983D48274CACB8815B415C4461F30FF8BAE32973EA90702C7F1A9224E5A09ED96813605AE1E30DC73D75DCC97900540429947F71405C6BF9982A0A5A74F5F7380AD644B5F7388D3A5469CF568908568A74061ADCD8A9AFADC858936E1D62310DCC65D2D08AC80B4370571AF00A34512B9827BCA502C39DDDA6CF7D037CFE916F59D98ADA34EFB130AC1D9F7586567D465B0F6CAFD18AE6564006BF3CF8B76110CDA146877BAC3B04D78D30E91ADDE4650C76BADD0E5F10A06006876FF488BEDC06B9536ABC89BD15ED7A0FE22B475DC776B3C5B79467C9210C031038C2297FE6705B3077A45B19D12E4F45D7811CA3713E4999736F2E383730F8C6D55AEB64335290FFCEB89B100F2634E6CC9FFA1AD2D86B23275DD2766F2AE913B56A4006141BE5D29BEF7BDC6270F646E59E622143AE301561FF8019242CEE4A94FD81056D1A842FC13EC803EDCC22A876F7A25D747E113670AD20984BA61064F269A884186C3D88FCD69F55E5A1C28298CCBF3590E1B33D829A72AAA380066093ADA5B04EBA74362C333F28BB94D1E357C3185107A3129719F63F8DA269BC3BFF5BE9B6841946B01EC7D8A279CD34C22714145FBB05BB7C642ACA946B92FD05D6B8F6EF57F8F86B23D457C21F76C2E48BAB1F9F6F92F4F54A40AF7D8569857589F8193BB1BF6E47B483F1AFFB06BC13A74FFDF10887FEEFF4A8AEC540ADEF148453C8DC896933889D27276D81E89DF905EF2A747D0AC852D5A2A4E5470C6FDCF0739799FFC202D1C47BFBD0DAD391988EF6D1AF89E0881CECC89E8B3AF80499F8014013802BEFD79EB3550626B51A742EDFDE4CA567DD318618C5EF9AA0841A812185F447BF776C42771666110F34B1B7392CBE1286BA7EF687D3F898324EB51967155A66D1AD5A9DF1D260E6D3B95A105D21E9569D81977CECC6F41A155D15D7FC39CA69450FD822825C931D376ABC7BEE0DF54726B253327EA075AACFDDFA84EF97509034481E53690F08F0B3151C6271C3F810E2046F4DC9FFB1636242318B4D473254CC2E3FE61DD84C95FF1C313003D8ED006848CC22EC1DBE742F3C2BB6A4080DE546203A5FC46158AE275B6B8874E09F8EF9D5935B2C7975078E0448768773F16BA5A611A367E6D6FCA3F89B61254D7877B3B32A9573B38B57C3A2469A5DFCA7C2BD90D5ED585241236A070EB8D139A9D49FA5732E36AEFC7B9369F6A9094103B2B06D9BCF21181C26E16044702E1FC1BA4B4FDC61AF57305860F70574792283AE773DD792F3F5247B07228666075C7299DE6FCC8DF07BF088008EC44593244967C74BCCA1B89F2917DDCCDC9FE030E7243C9899C28A4BFA44DE8F321D0C0147E4C26697EE81AF31773256D8EABFCE1833ABD047DB080CCD5E3A4E50EA175C6DB6B69B6EF86392F58374A2CE57AA34922E7A4A7979B8FE80315EFB817632A51A01AD821F51FD927E244AFFD9883D2B5ABBC48290325D1B39D5ED6C8AE1B73F834D2B0C8304F057B10A7CD695F1D46D9DE8DD38B6B5F3450D31870FA8AF9CFBFADFF0AEF6DB1437F37B3884776A4CC0E5951B27DDB11F73FC21E7973BE3D2EA8EF440E1B2B903B90FBAEAD629F066F4C8C2F37B992BEF3BC1A268F99424F1DE526274DE5E1483D811B552CCEDE116641B7BBB2DC2254D02D29FF63591146E6286D7B84054827083EFB5A0E72EC2DAB8D8FA0D038678B50AA3C960CF98738DB19C818EC79AEF5E287E42A57730F0CB336E8FA81FC60BE3159E532FB3ABEFC6952CD0D7FAF549488119D30E6A8D545ED37C0BBE5E08BA82747B8310E27078FC8DAC32CC8EC3483E2DEEB1D9487F5E99AF35A02BA77C72C7F95F9B4233763632F21C8DC926049373A8B33BD1D0B3E10FBDB78619A26B191A7659D02236F1F168BF818C2A8E1C50364F4397C197E820C981D23325DEF6B37F471CE5773AE3B63F74D2BAF17C63E1F1DD650F56EA60375BD00A05ED30663A7A72098E6F0D2E9B5F998121CE44F1D487BF648B19702689D58CF3A5D54250C195A1FFE251EAA055697E807C7435C34F76E0483EF2FCDC79555B7810639719DF16F657929367B5CFFD5687F5EA7BE44D9C563CEF2267B7ED33934A06BA9EACF1736AF7588360A5B3D06BA5AAE90F0C9F6132AF0F888D4A0E4EDAF2391A853AA2007104F71A46804C65C57D6D7506C726CC658CF441AD627862129E03AC0A784DD81AB0B036D905ED27F0BCEA05AD21EDDE9F4393D313BDB1213BD01F34A73C21AF821A96F4A6C510F85F07FE5E84B33232D3ABE92C27D0F3B1A557FBE9FD4D2FEF1A6EB9DF53C0898870BBA4066F567837B91D82D62C3C1E3BFAEAB52F4E80B3030E6EB5FDEAEC5055821569460CED9C5A864969B1513D5DCF6BA3CBA3160C9DD901B0F30BB1FF8123CED314890C8ADFC58ECB3CEB5AB5B4027966FD6B0F1E29979E75D0EBB32A25C335C466063FD735680FDBC23FA33C4FD29CA635D6C3B6E835DA546142F3EEA1500ADF27AA8750A8AF3CC022F75403EB00B8C4DA547A30A25D6EEE56E887722931D316428F3C089676FBB00765135623303DE8B51C2074674D6E64B21EC6A4604426354E773429E96DE0FAC9E27D11C1200C1D8CE7B74F67228D76B825757771C4928577D4772F4EC9D06C6B8797F4C3", + "context": "F29200E32E0DCCC92F029503DF3C878E340569C54169A31F7704A16EEBDF5667D0A5DB865F486F67052B1765B772D461C9F4B75B2D6056CD185F2C9D74D1F446674D75B49D5082EC6097269837BC7FA3A2163FE026BEB4455A79D7E135D0FF2BC44E213715767808D4DD1AE2A6B147F041616D74FDB89589848B2B155957CA7B79CC497EACC1050F8A3C98BB4839D1858F", + "signature": "38F090E96F70C349B2F7F5222ECCED39FBC011D899838B6385C547C489DB07F7E16C67FF2A72B3AD6ACD4FE29B265930C2917608DE48576D9D2B06BB314CB1978FED83802C953949192E91809F10231643F29AF9F24B0919C2BAE2A1B05B51EE4106E0C640211A4A0B2D7FF26ED68A7C03D8F2EBA9F6A815B81DBDAE108829E8C8BBBAEB5F9C54CE944EDA9F37EEA8D77A8EA5C5932E19F0ED53DCFC17E0D4C9A09F7AF37BDE11311DD7BE9834DBDD3319E1F2DD4F402A6A18318353066F0EDFDD2CD1C6A068BEB99E58E5A974435B598F71DACD3C5060E840FE0245D4C1B4B216EDB1A182CB4D8EE4B87D46556766CD189A6FE5D0F4000EEE20C5017BD568AE4C280084F785EA40E4BB64EB6DECDDEDB795C79E36D0C4E032E0314DD92155593F3175A49B3A4D66FD6380AECE85EC06512B6A3D28C8A132B0C9571BD8477468CB0D1F5F2D30076F1FEE8E02D859275F935D06AFE92956603D173D9022D2F1C9FD66E58EB712676BBB9585395EFE1E70BB62CB0515F25639B60183518AF6C80B0B96061E07D2B8F5EB9F88225258F82CC1D6A165CBADEB7C1DD015B9D4A09E0352AF9934002247CA179F5629805D736F531D4453BD9F629EDC5CD7905FE3C010B39B572D52938E62F1341A34A9EE7BDF348B84FEE3C24EC1D13E755377CB6404A32492787967D8DB5C05E42D07843412D1CB9CCBCB6A36DEFDC69DAE7F0012DD1ABC4345566AFACCDD6DAA7E5C764E87F14B636E7292F5A61D33D9F175F31828A8A0374B16C9214CFE9E4D543BC1E036C05FDA1E99F595E3E67E1EC9D6126618F2CA820381B9E3A73D85A2400A226E52508E6D8C9D2433CC30A482F84F6CF13345387693B15DCC704A9917348E0BA630F7268B819572DAFAFC0E81EE012E44F304D7EF11CA27B14E32DCD4628BB1AFB620E48538AED6296FC0FFBF7ACBA790A7559B3F232CC69F506201A85C5740D4106477496028663F215ACDB13AEDC7A20CDBD25788C3E9234A17CA60A04EF044877C66F2CA58432FE115C83A8430C9CD7D28FAA551A8A7971F6CF2EBE686260D08537ACDCD93621F883AEA5E020221CC6493BADEAFEA26B2AE3185707D9764FAEE3487D2F9DF9AD5446661A0A46B4F7257FF8CB3DAF7C86482278678388AACF10EE5E3DAC609BC1952F8264CFB013A129A522696E62D3466934F05CB1F0EBB14270AC48E0019E98AE58119A384C81E1BED12C3F49EA650029D4A3C04E093F9617346DABE1F9A71D629BFE2A67812D6441557BA9E055878F81E26136A0A475443D03DE5FE34D5027D380339FDBAF69A0F78A112C8E2C085C37101E424E35E4DA645D03671BF030253384364BCF99F27135671071ACA1BD7D402FF513BE24407E400495C2D3635E320BA747026EF4510B3BBE22B3266CFFCC42A587BB8DAF9325EDED5CBFF9213F5616D935C2C81D65BE85662D47A63BC71A97FECC21DA89EFACC389944F0CA94604A6E102E57B7D2C567EE185B1EA07F2752EB85D2B2B90056FCCE7D0FA40DB9D189C88449C50CE78884594081C0E0FB835EB919CD9995FEBCDB7177A84213809CF107DEA6EC7769F24AC4C7047EF7BC5E365A3DA7A074B8CFB5FED85539AE397CF7EBC4B55A99A9A3AF14926C59B487F6CB66170800B9A5FAF484B536B608F4C1AE5B7B6CD7AD09003956300F36D040A7CCE76F2D50F63ED9CB7158FC058ECBECEAAD7A02FDB6EE5703D451E4C832A02972934FC34DA1C1F6680DA3B15B1877B4CB25E05DCD32EDB00269042384EEA16C758FE1B32121CE692C5E327C2B93E64AF2770C2FE2B88DACC96525FB46A7EDE1D43F2ED9CC0122934514B95C08F1C3937FB8DAF833A7C2E32A26600F6C2A69667F60A7B4B81115FD32AE3577900E8B11F17DA57CD4FDDF19DC174427D05DE53D276F755AA8EDEBE83AB4A82D58349C6963F052DCE606A8A06075D91BEF2F16CA0178899FE4F3E7DCCC7B0854A31D95E1AB4019B834EB3004C4B7A63DF73C9F7CB0CE2E9F15C4DBCCF206F93837A20211EDCA3DCF5794B77579FB943C9584F19B48C14790384856C4F8A6AB8783C9FEC7FDD53EBD536DDF2EC5090F160CE1A72E346303CF647D1BCD8B8928EFDA579EB69CAFF51B58A77F48345D4BBAD29163DC8D4B9B566C2744DDB4C771BD38CEBA46DFD494655AB66D075F12636515A2688F23B39E92B63219A599CD1D15E66354C218BFC3EADA673653812BF3B2E5D760647BFB2C65EF64363331FA54609C3291F3285C3E76A5EA81E02080B2055BCD4687ED8DF984E1FF9859B1B85085732C376DC9060B1787C3A778E75B60DF98D86691D626BA71E50B37AB9D4BF96C4195A6866859B6EAB710A2F28E62908B0EDCE20F3EC5487B30BEBDA898A7BA70878332940FB4DB2E6CD8D248D6E0BAFA91FE51CD6C9057FED8785E7E5631424487A8E8CFDD5EE78A3C8C8FC882DA1E27EFE647EB0529553EE371B3E3AF6E5135CAB410F1444A1153B87BC8AE91782115CE446C4E742D539710D2547C8701AA5F865D095B0299B2AFCC22A35CBE23FC813175EBB281CEED1233CAB85005A7DED484E5AA95C72DCAF3BDA6C0AA3FE82725ED770B82DBB5F79B66A17CA41CC7B07D9AC34BA7C8717035F40F3CB3580B1DE12312B149EAA52240EF945A6EA788DC3BA82ED2881710F1FDCEBB1D6A50CC0C28018A2837DDBDB19D893305BC2DC5DBD2E1B72DD2D372F3884F794B49330855351468243EBCD524406A04258961A0046B97F2FA0A4E575F46C823D92368C63D80B3976D6DD8A109CDB54316477116D54C3FEEF3B19E4E934D684BB18DDE1B396FD056733364B24A2822B7F1A49DDB2AE19213E4E22B4D00CEEE93DB74FB1B2230CEF71A2BE44868B0BB0B905DB49A0B19B55942B2603E46E3B7D34AA3E5877E64A5D4EC487A56C3DAFF772176267241CDC520EFDB08919595D67C619121F99113CCF5C26E000DEF5D4202261FAD2481CC8A5FD73889AB3A565839B925883BCF44B404A2C6EA39327AAB30A0E9407BF7DF38538B931C16589ECB928623C44191EC1CC8CFD4069C6726424F2DBC11C0D81632C23CA7676256909917CA01F33070489842CD4FA05BBDCD3CE84223918119D3075BD69D9867A790076A3A31E536A712A75542DB482CB877B9DB87F04901C0B5BDBC65BCAC6C5CD7C1394AA497AC6845F850BCF6192F0060F46E2FA343A57B2CF025B834C9D1B0406FF538B2A2153AF183F15D19ECDF1BC9B77973F5C28900D0AE94BD0C1F178D2D63F4662D2DECF1CDADA12237C0465F502E4066689B3617F48BD9D15974430E173153E3FC701B3FFD7AB15301E7D4BC0CAC3ED9B14C188DA2EED61F9551EA3E92D3E0F9D501BEF5E6A9F5B128A49F0C61E596DDF1EBBC500FF011BFE2FD9EF86ECCA41EF3C0FA68EDE6A13BA13C37B96725C68B053ABD5C33B650BFB15C6FE1EDB015CB652269453E4182F8E1B973FDA6CB5471A092F5A30D4AA27787E7016936743FB561A0EF97480885EB9D83845E709A1B21E2A68AD5EA5F9575AA6E47D0E256A2AE0FDC35F0F85BAD7E8B79A82870D8664FEE656EB77818D94B89FD040963304A72885052A2A1E26DF98A8D2B3A9B51EBB126136331033C4D1E4D90593C113D8D0F373F8E1ADC38A6CBBD13C19432A975DA09FB8BEF48B7E765DCA9ACB68B4AEB56654DEA51127482166D0FEA00DDACC02ABEB9CE08BA2E88563EF6B77FF1C1D2CA73B3741C24C4B17EDF628E7A0F345352DEDBF68D0CAEB1483F199D625F111C47583506E2B5D2B7B1616C4C8828DF9EAA9C29C51FB6E9C9143927080C7209D4A77F153780F32EE812F2C48D6E02BE9CAA31A0F41465B2FE31D465461D22B5640765E3942CC8571A1F181F1989ECCA3697119E1D4EA7AC3FEE3EAF8F08675625FE32F0A15B848D54940B4E5DBBBC51082F425B927CAD8975E28FC953EAB8874060BFFD1E85D81521549447E50A981AF8FEE3ED288D51DC352ACA1CFBE33466472FE9F8E99B25D134F5880E0BA6695DB585608F3BBD322649FFAB54BEF5BC5757B2B6CAAADD6FF0BB303089FF8844CD5646F4B48A796AC80E908AC3D6467281DF56A5FD5748059F9162B1299AC64F9EF2F3BC528692E38EF62D7693E1DE24E8C5FE44CC592261C4A2366E5330090296FBDCCE7CE37D4A4091A3E4BC309B7ED5C3D5A41DF55B8D6C40F170C78E1C31929116388E75B116588CCBAABD7DBC42B2CC926964DA61DC83FDAB47913D781437FA8556BDC2C5A366699158C72F7C1E48EF4F0267729CD079D1C58F10C119D455F701E115470B1CFA37E70699BCD5D9755D9C6E0190B8E7D08277153D83CECA5D6231843D7A87C8F58A08C379401D521DABB22A0DB46CA58D45B742C2DDC0260577F2A9AE4C30FAE2C40A899D8307A7E608939A56CC69BC8F7557C8B397EA6141369B157160FAAE6988D6EC508A73E454CE33D614DFC3AFE8A374F6E2B5C6DA95CD1FDA7D594940F3C03377CD330DE5A9AB9EF9410010278D896E52912BD4D9E1D949F95087D0F0D204E5EEFD41F392FC1E6ACE54F869B28A872FC0245F67196E316406257F338D7A21603A49BC6FA3C544C1CCC8CBFB53E62C95E48F8327576CB7E17EEFF44C4EFCE4D86EAF16FF181ABB3E5CCEE978094083BB66AC257B12C6A01036856A5720291DD4415EEB4EAD88D861C444A89D0801BDD9D67C30A7163AE2B606C7C9A66BE27ADA79278B045CC33612C3B8D321AA5F3DDACA21084BC8E9B301965247B67A052C89939A614664AAE0AC40C98CD49A1D50C2D479675265CD4E2603A5C61BF9A5D1CE2F50BEE66CE5C243A97F24A6FF442F5EC1F01EDC0078D3009A474559B2DC576F82AEC6C078C25ED96987702A2AB3F8790620E50CA5CE5A99447F4AA5B681E79485A1A0B31EB8E4931167BE532F83402347075AF716B0C3D62814FDF48194A0A5D698127C7763CDCA9E566855CC7DB4D53877BE297A999F3E9D1FFC5825F0223A5F4733BF3E190F33BC19DB713F5B6FA71D06668415A041D8D716E1C33F127BD41BD63C0E70DEEC1F0EBBE578D60C96511EEC10250DD6E92834EB4F07DA57EB985F61473A8E2426355595C938B6F11A5C18FBC9EB14BD34B4BC965138171C81589F565AC4EDAB623E24B9C5219385D8D2FC3D7D7BFFE830E48BE5FA8A7846A19668FA7C2F92856ABCA3110DA718252E00F6BA7723BCE9B45C89B9123E5B1A85085D44E7587918F769CA156D92E068CFBF1F572DF15EF49098888592D10FE33A242E70E899959E9C52AA18F2214E0628D44C4DDF32B22EDA8CC88A4D5A5B68422926B67473E5516BC36959A230FD9CBC50E80C4C06FE5443F9DD4FC8F9B2D7EE2CF93D5C6285685DA5FD28D4A124145A63869902CF9E255286B5CC1B5AB3CEDD0B9B7B16663919737AA9D4F98F50AF37CD0B7529566BD84DF1F26A077364B654E9E1EB0B83DB186FDA8A19CBB90A79E32F329F58646C66B63BA28D505CEC735A098C784B113155C924818ABB449B0DE2F97DAB80F266402AEAA921AD626538FA40A0B4F8D6C021DAD976FE94326AF49A1476626BAAB11BB166173837A88AAF292FD5A4AB9EDC971A3133FBB6BC3ADF9725B38E8072F9D42D92EAA847E514ED12C003CFFC1E52F47380A66694A6DB3B5DA4F05A128B4100C6E5E8948402165CBD2C363C440FE225DC7321CCE2FCA9E4D7FA1667D3DCC16BBB2C37058E261B98EE6BA8830B0F9E8617729ADCD2CB13F2DCB6C600A5B17B28492041A6F2A2E4F1767F5B18458C2BACF45C1AF60C7215D8EA012B8350A1F93D2F49564C1B354358998253099C10BDC489DEE8456A66D1E0DD7589275984E0D2A5001C88760B70B4DE152DC6A3EBEA18A73009E99A5653A5FA8501C8A473D49AAF3E00C63227FFC315B31C4617B8B052F4806301C78177CD888058C0DF08D60F09F90FD54F09B453C3F7217A8F7F7928B669DBD8363BE56AA18547E54007B4D30F5AA122555EE5318EEAA96BBD9A5ECE5E35EBC5693D8ABFE7085A07E375F3D05273B8D3897F08CF722BADB78D09CCFB10787219BEE2B93795DEBC7CE1F9DA5098C0F0675BC57A851C99F49F3E1BDADF6C547CBF265591D171EC5BAA0C290ADC2DFA44C89FABA4A39BF47AA16B06BD66C2FE9958249E10AC7FEBB4E554131E948DEED24587062FBBA41E6CF0E1E991324BFECDBECE7808919024800D36682FB3A549EFF42954F79222F35D26B8AB6FB7B48D1591DE082F16EC06237C7963B4458A32F312AEBB2956B163BB9E3D049887D426690D6EE668F061BEF185AF0967A031D0084F919D4C837716545C50932145A412AF384FF08A88132F298CBF58C4661D235D87D3FF8469492A1A0B99F44609E62540F2EA3392843BDFFCDD68AE61BE06415F926E34C969686748DC005117F87286926DBE146A4218DC18512702C05BCDFC964470565396180305A72E3902C746F123FE5579D4E30DCA4EA9D18E7584F92DC50653D7E9328306C5C9190139EDF51723FCBD1ABFED644846F3D156B9DB3BC3A796843167DC06DA16F7DA4153C160D71C56ACA3612DA2FE9F4FF4C6CB00993D8FB2D63ACE78B86496AE111F8F32F1111FBA49B9BB04E1C8324E204780581D219D7554E54E5A1AF6CB1CD3F5A0950151DE8F12F35ABD7B3C633F412BC99233680AE1CF27AB56B6B30A45941711BED75980471EDE1C507EE35C710F04987CC22ABACAE9CF316F0D8F2486FAEE98EBC522EDDDF636D06C7EF1CD315A56CCC68A31ACE6E426E021DB64CE9FA529A70ACD9F1F7A0BFCDE17B53ACFDC587B7671A93A76CD4BED047BC6354B760526C3146759D3BF9928E331ADDABF3E9FE344698662E05DC279C17260EC19BBC5B5291405E2396BB303BD990E1F012A8DCB1BA80E828CE3C3E2359BFF84A864A2F919AB16275CCC47F1977C22BDC7B43195DA0AAF0F0571ECA573FE8342DC711365BF75313946D9E5FC4B66E47AC8BE8D4389B19A95DC74AD61A076F4AF4338922D2A65809B3C0A1C9A50F850C7D28BC8C786128A5AC2E7E8302BEEDE6896C3B30D56E9F5774D19953858A2A437BF07273FE086DFFC120D48D9F7B522F046EFEA62AAA78EB1CD6E1A4D5C54815DD3404A8560890DCF2A2D272F9C5973E3E3D04969DC415B9AFD1E908BB135135146FD93C906E2AC05A67AA4732ECD49AB449F87F8D5B84A5C2F2F6B19B20D1681DB75AA4E7C327D9075490D72C4582451281D164C5C0392701AA6940CF26B81FDE21C0FC6B560A047B46641858B08A3934188D08605FF27D201022BFCDA5E5CE5A8265566DA0641245B86B5D2BD412733ED3B906005935DD0A8A6C6142F5502A6BF5432FC663486356CF221B34C22850A4ED6D3BD649E1A5DEBD314709656CBC4659FF3E095B589F73D84767E3D3AD2FF112DD0BBA217A46A52178312DA2B0B125D74C0C75C481AB819482835227A1EB2E7E720DA94C790355A1DD4A10467401A126D57618B2C6E9EDD785C3E37D218C5D52CFC50DC13977A8083BC6E529AF2DD49700420DD22C99E388D1DD70848443FB6818035D417FF815988765FCE05EEE33D6DE0D56155BE38A6E9AF293D3E17E2132477ED4C16E34B3BAB672C667F8E75F3AB6D85B1F2E3D791CA2782F473079474E7F58E23A163B73CB78964868FB7EB43D775B10FA071575D7CFF205E1999BA7433F7CE330553C03320425CE9EE8D7668928ABCB4E63991D966BD478C6DAB53202E912E50D1822DA47323CD51DC85A916D3E548A823ED4DE8BB911D3E0F0E59D74B7756054C91F10603B88F3DBE4A1456ABDAB3586F4315C1746F7736513CA70B18CCC3A7F157D66340B3A2140BE038DF5CFB90DCA603B3D151FACE585BF321D47B0FC4D73286F90703FD01C91B06D7C55F10B9901ADA34074F2935885FD304A651988DCE2BC3143BEC56C5D2F4937A10ADF5545FE9B9B5FA354EA8599ADE5FFD86BB4B27F6F20C02693576690AF2EA18091811DFCD31669B0013257FAC58ABBBC677F93A2CD69D1EF68D3088322FB37D4BD2989E4EA467A9119E75B423999E93B461324FBBF260C0E352F5E2AD8FA344C23015B0AEF5D436313BC6A0C2A3556D5C9458605AA3D5A23AA45B8EB75996D1631187AC8E8F9D8C7C32459C5EDB95258BCFFBA4BE45002FF28D627CEDD9D7318160BD96F6D43D544C859D51AC224D17C1E9D3EF72EA914ED718189BC90BC051CF09A4D9D5AF393D3EB6EA4F5644211913CEF6E935B8088DFB918EA76CD5A5BD4118B63B2B815AF5C2114C61177F59074E620F352C1D6BA882564B7CD607CFB6EF1078E7AAF0B58B581D7C3CCB9A4ED61F098ADF54B9118768FEF4470C8EA98D3A0353BC507B66558F7FFFCB7F29DD262055F233548C6DCD88B74D2A0662F20800AF1ED2DC58ACFDDE5A0B52C1CDB07FD97084CD9DBCA9298BCBA7A1F8911E1A3F1DD3BB27AA07F7B2C845191459809F0BD41159AF8EEE0011456923FC582C59A0AB292F6D602836A547ED643604434729FEA2B3885C637137B72AD0ACF4BB7E840BF046979146294CB7A32085D98813622F54C315B66D81F39F40985E5C2C62D608323385A55B97A1A92543893DF3FB3BE9853222372BB6F8A58122778970745FF9F00A8BA1721E16A02B744C39720E228A220ED7A20FF3E43577F84E6821069CA4454219C15A4E18CE30FF150EAB6D1BD3F0C3321D9AD6901DF7DF78A3F04EBEB72A779E8739B482E0EBF19EE92A77D4F1F7B60E667639ED50BF66800C70932400641AC6D9266A050CEEEFD896C133FCE307CA5D893D5F58A459661DE15DE42D2183ACC8CAF8C063876A1773EEBA178FF389640777E21BB8113A2F883C335631F69D4B0A9BD9D23CA021D01515A266E11B282649F7FBA0B4D86431E269189997CF518A7B223E4D367E843B175B10D459292CFCA91BFE239B3458DADA6F695D16F92D1815D5E80233F0C134EECBD66FE39303568D87F87BB421A7B1701FF6C60194E63B021FD368E3D2219A528C68E956BBACF423BCF5F22A827EEC3CB9D14C4CD415460812B84A91A37919AF0F9C45670081394D209374B461EAC561EABEF8524C2905A245F35D771DFF96DB98981D2CF1ADEAF51B15C01FCD213CE8C06AF68BD195994CFB69B459B7FF09A4F46991FBD2CB6037ACAF62AE57CDDA816CCEEE91F953E3409745D7AA57C03958B36E2ACF029ED359E9AE338D4A3FE7888DC1E84BB0A1EF8B1FC0C4CDAD8B176AA11B181142A8D6C55B2890908932C786086E42FF00B3AEBB7B6385E7510723CBB07F5E808B8EDACFD33009770DDD2A6B3A96DA93B36FAE37B4AC56BF47344CB2A709222E9879E59CE355B8E1B3113C2DDAB6B23386316F4E48C8024AE5FA155BBB93FDAA73240743B6A08A32D17B77E45B0DA43B38CA47AA1F56F6722546640A44F207F249BE9F9C7BADF501307D377E5DF7002A4C8487112FA552C08BBC2BBEC4500BC9B98AF1A417BAD389F82680C40750311A25EE015D33A3B3A44B2C60D531A9AF8844181B32A24FAAE10A4534E7CBD21A706AC8600C2B3D872F0CE46573EA16B0EAD4197B3D6B8E1449880163F31BB4A6826E0AE1A42806583FA8FA7D8BCA379F850B4F2A34348C248889E9AE8901228B4E11938A56BB3CDB41265BF03EA8E9883896A4E07B565EDF69221B3661AC2D41DE055DFDCC7C48CF7E1105E5FE59D5E4561E3A225C9BCE4E7D1B4CA91E3D00E6DB85ADB842975B1197F3C22DC7AE98AEB9B505C78EEB526B29473100CF01925B2A561EDB10209186171112C48F02D6D4E9945A93401B7DDEB06930D565F7AE3D30AA284E9562B858A3A5AEDCAD7740ECD19EBFFE4801C419928B74406C232CAE9B8FF1ED1E9950E956A901C26BCAD903E02245352B95DD586414DCF692CA33D302A37744001AF0465853FC76564E9884D13B60047102F4087D447FD7A59834B66ABAC991A0E4C5B697865DDC98E27DD6E2A2C8456B5D480186668A1B8435AC2E75E35B137F80D37A8E8BABDE0D121F453BE1F4B46F4D3647AE37EAF7B37AD34BCF74118C449250F9269066223CF995C9F1773619217B5BBADDFCA61F5ABBCA86A669F9D5229F941B5E55B216470F8B75EB34320AC929DBB5F6B4BE38F367F7CB090AE85966FCB6202EA50164B75C592056BCB50879ED2F68A9B8D859DC1A297932AA861454D6EE3C0C522FA095980ED53A8D766AE0A0AB4168DC552A5F7F48C08DE36FE01E443E697AAE13F3010060523E037AE20468D50C003BD87BCEF48D2A4A4F1DB6C92D170F67B544C9A0624FFA63FE49ED6E37418F927C004BFB4BC1CBA39437BC0699C1228CD7F16670D10C74649F5E26C126CC819B65D7FEE2917BEB6ED160455E4F47E71D0E8477A2746CE981AF889628D0268D5B3ED4C9D632C0A42D8C38AE428AC6B715785755B90C4816E1325FBD862A3EF322FE8AA93507C13935CA8BBF068D135989A11FAE9E98A4494154E3D1AE8CB6A97BEF3D179B43C815A513D96CE4BC9B0F47C5C55BF0D6573D2A36040B6C66DB9BD4BEBB6226363944C8CF0B8F989BC56A67C43FB700B850BF4ED52857E216A4604B957048DB6C79E6BF199592D3167100925E94051399F2DB38639C05BFD3EEF8C5B985F975A8FABE32909537239421E81BCD4198D1E51062355BE0DD16C5025DE43944CE46E528801753474624155BD98C29451E2A0BF51E2CC099D80F76284A8A0445CADFA39B9AB8D4201AD00AA424B8B6EE8CF2A815CCE0BF234F7F42E07BB754331D7E192D132960326CD57BBD56FC5A9EA2BD5E7C87ADA9F061DC9EA7E9EB88F40E899E44D3558FB9B16C7F0DF9A25BB1E5EE0C6C3651D5861C94769EB7D57B84217DD7ABE8A4386890487E70ADEDD11CB102689BE77A6F73BF11287E65076000935EDC758E191D6DDE89296675746AA7CA36F87FFFFEAC5CB3F9081DEDA46C0F04EFB15F980FCC2C3D787F81DF668AE9A10FCA5016598D24771FA93AFEF4284E4364A0D54FDF5AB780E85CDEE0554F26C870B91B00E945AC32415B23EA3EF30D6E5E48EB871E83B16C9BDC06B141B53E4C09554DF988170CEF09D3CDA9E5A6014C1DD5B74CB95CDB94CC24B71297E8CCEF720F1B0EA62AA5A53C730D3DBC8FCFE29F706C7E70AE7FC258D6CE7F9A3722DC473DD3A41F807C4D6E99AB2FEDB28039A09319EDD077042075AB4E37E03DEBA5E875DC2384E5AA5C32F68C2D385E9B4A2B46DE6BFFA5B92E105F44CC41FD28D7080540B342649710EC859F9E8D06C70A1DC7C28D162089C2321A899320C1CB75A0110759E1323197738C775C7B23B8CF52F24872ABF4AA6D37CBC9723E9C32996C2DB34346A3CA44924ECECA05916C1BF1A64D817EB1C6CEB1B831BFE6779DFDCDD3E5E04328FCF3AADCFDB7EA1A70E1D461FF3EF600856D4F13DEC927809C9B060605FEA3A1C36477C833EE58A3ADB47CD339C23163ED28F65C407D98D2EB6B93B17EBA8B0C80E789090FCBBA28E9CC9257764CB505E071DEB6F6E1BA54D049E68BA58C12526964C7028AA1C0D428451A6E2B9539B83F21A161FC955247441D5E2F5301D588D8EE489C6ED536E20E155E1AA4558F3C9C04E285335DCBB314FE9F5EA95984A1C4CCA1A6776FBB09F54216647701984F8FC385E61B36E22BD3D247D21E0AA0C4A87024BC80D0732D48A153473765A8D27D251DFA48CFC087B494E0B62885146CFC6BC972915B2038EB699CA389367479EFB41E4AB283238BF18EE953D1511F0BAE0F0D81630E56A727CD9AAC85FA266369C62A1C2E39383A1306091274F77C0F120AAB529965778FE143457A0D0B7DBD4AC56D1AAB570E1D4B74D3773BD6BBD7CE8AF97B8E892F222290AC5BBB83AFC2F4557F2EFB844C9F460CFAD1CDE9E152EC52F422A6905F280CBAED45BDE910DFB2950C39C29F80EB6872D14DDD5F88FFBEDF79A35D16DFF7944A292BDF1A1D104C59B4B2DB55CE817F2AF539F0D5E14B3F7F45E8812F519BEE3A24BED6FF6C6F42306BBEB0FBE70F9F8E74EA3B32B1EBFCD9C2BBE30F4A87A4A202975DD0BFA6BDE4196AEAE10DACA585CCAB4F4BE966C1EC2180892A05706721B2886DF16E22121FA7FDC833379B0EB33E3711A6940C1D345F19EB1DDE0923EA8AD12ADA44F31BF6B29B4A84C3651408DB9E2504C0A6DE81730AFDF959A149C113923F4F419ECEFA20CB79EC52210B8DB66C126A100AD2803E09ABAD29DEC3346A9BA364657257928A905225547501957DC73004B7196ED6BCD4EFC981EE8A2799A53D2B921DA98E9924BA7BB2186883FEA2AF3C06954236C540FCCBBBB470F73D219714C7B811233E153711496BC1278B1200927B79B11982504E5FC53B85E23ECF435570F645D5E9353E9718D493FB7C97CAB597ECA391DAB8FD534CA24B85891EBDF4ED9810E5EE38D7BF26AF7ADF72C5E35277E51D3088B4B8FC689237D12B5A3EF5644DA9FD7026BE2CF9569716E758208B1A5735DCE516DD14C05EA145D5C803F1C6195C22A088994418CF951292750CC276C819F5B1056B1D09DB11E8B37C79BB06DC2E2597CD5B9C97CACFF32F8F9626BC5D3E279E036BE74A07224E6EA8CC3926ED645C065E70D7610DD1405C0D1137CE799AF66EC05EFDE745293F28185B82BF835FB3746EE6FE92ED0A73E6B1B3BD5797E489B1C6E21BA624A45F82CDE96CA8976052BCB06E065A9C73AC9F8200DFAA8E754F5A4145E2257198C4123B88796CF77CC9B655741C9D0CC7C1FAF1CF4AD364F63EC10AF2C83AE5AF873D7FF97D51E0138E56EE9307E4559529DB5B322F5B3AA59202BBCAE8A2EBB01681E90D59EDA37BA4FE66735AB50C33EA54B3B29DA44E12D3380CDE5C7DEDC302BE49ED7C3441C5E370A43D1270905C978245B9598367CA1B65D72D757D494DD14004C224823A1658A745F12D009AF70137712560A5FD178DFCE01A4007B774333A8452D8F89749DB2880B98C572EA5B3BCD5E8BC75C74352010AC4DFC5F461326BD1CE92C87E54AB7214F7C8ACA886181E9F002E324C79D84938C539A0B5ABCEB44E5BBD3C6502666C10922357096C2DECD51CF76540838EBB60BF3A3872AC4FC2F2539FB690FB5D655295C772FD529CE9856CF0D4C22FF1B82345294209A12F4B9EEBFDFA07945C181C86E883908C0A662BE08B4B863287B9E9E05F51BFABB1EB3A342979E644FB60E84547FB954385B8BB2156DB032C9DBB5C3F0EF14E84E30A884049639861738D0A8E45FBE326048A98ACBD1398B525CF6435B73F1EA9B7DF96176EEA260A0000B5C9E3FA3FFF764CD1ED3A06ABE2AC20A7ACDEC2F7B8C4969401E6360A0347AF0C489C4A6773AF4235C39ED4637B29028EB73C0EF93C4E919ED283D59A14D941AED8B9FC64F4A2154C9C98118E5F8913454C0033672D7D32AA2015D28AE47B7DCE093CB83068675001C932F8EC4A7D65DC583C5BF53F180F11EF422A5D6CCC5911F042CAB7FBE1507F3DBA86537E71935753514EAF4846ECB33E82B4EBD24E8F3241E92757A9ED4A75DF774EB8A5507E90AB9FF53A344F08C31E2F57F85B5AFD271DA594E23A67C4175AB6941AAD38889A4C573D5A3C93DC70F244A73DC7DECEDC15C260D3901A3C05BE5F75E536AE4606773B0C43897146155C71D1C00DA2A526F8677C32CE4A7D76879DECAF36067E4DB2CCF1661B44070C00D01C29C9344C243D43470840F11C370F642EE44E72C693DD5C0FA17C3B069F378776B73044AF8D483F14C8FAF5FCD7E279F9CD18A45D700820958F3F98F79F843FC0599BEC3039CDA8CB33F2A0539B5477C84EEA9289651760D2501DB24B3C661BF018EFF817203310DEDD0C2CAC6F3F6457235D84A8C626CD2565F588ABD70433BF419E2CE25E0F96DFD1FB32C6307A1997B7BD275C3E717C9681D23620259964EDA5A7C582B5C616D71D0C73C9D1E4E21FAD548DB6923D1BFC5D74523C4581EAD84D37C0F0A8466760A7439522C3291805FE11CDDA13C0DBA94210C6A8D7B24D66F2D79D771DB1C32E4A06598A1585D4C79CC34354AF2ECEF5995A610C289F37C5F4AE7B550ECD60E90CEB69D21CCD366A29D70395F0DE991A33B4D8800EC0E444222D8C48A6BBD958583C6BCD68D27F618F6FED62ADFFC7B2817CF397D17E411F09817E1D7B2CE4A965571D7C8F466707AF0AE244422F8B303DFB36AC7F828B0ACC732C9C3BDA6E0677390BC9676FEAB51E631D9C53811F002731EDCA721FD098527DA5F1FE6541DAFA880627BA365235D33BCAAC78B82C13587EFC7A0B446EEB3E52464F38E3F94A21E4E24F8658F156D957D1FE636DB6D2B72A14FE84C427883C346A0C641EBC5655AB78852E9FCCCDA6A38E85E4D30F62836A69118D339722E33CED531EB8B3735F5D84D95AD1D6A16ED4CB070C4823F94A3E59856C71DC1B0A4C7D979D5FE84DA3AF3FBB83C6E5A21E86E4D137615DFF6ED022B478A79BD6801CFC6F62F299C0D1CAECE6F98E99E677A7658F239BDD6F94071BE816267FA850BF4B42F9A9D90A7CB7177CF368A8379ACC76DDD4229039CEEDDE89560E9054349E77B17ADE0F45CF28F8DEFA425153347F78A858A1EB832AFEA3CEB72DC3907A399FA9EDCDBCA5B6062E4AF5BD2028B4A638C03E1D8B5D45E5B11D1FC50B4E2CA2AFAC3C5BF9783B7848E1090B9991686F412B5CE2B7C8DCA04310D1B1A0BA2E3BC0F0B86E974A8114A37C35C72359AA63D500BBC50DC511FC0B3A54627D708D8D2FABC297185DB7B279D1337B08E1FCEB30BCB69DA8A006527E519992274465E50DC2E1627F2756FEFAF46A150ACE7F1FFE8501112E4CA916482F98051D84E3F0D0B9196B7A1982A3B8C66E6EABF58B583ED704FF3584ACF7FEB5DD62E9542C6FDA2361CDE4BDA77C547D34223C9A7A4986B24FFDAD6F23E804724782008FD804B70D291E53FF44E8861D9126E9FE2B377A48CE0F26EA453E877064E2038943C138E0F3F2A550AAA60CFAD81B0E1A0CD580EAA15D48883305547D535767FED2441C8EFE1E5067B01959276B809E989299FB0E5C05FE9726A1781414009A1304D7AAB4C832735F26B7C22214077629F9769C365DADAB7518F547B1264B823F23A16271142D29BEE48AD5CF51FE699AD77BF1CBBB2F1CB25D80D7CFD40EA0E4D06529D720969A1DFB52201B411F4560E03B05B4EA7A8CEF5D4A3F7D31ABF367210103B0032E77A3EEC1D97E178D038EC58A58B5EF879BBBAB53FE1348A15DA6507BE2CAC71D15357529CC9602E7633530FB0ED98AA70843D86783AA3C21B33515F19EC0DC0C6BA5ACA96BF38E04163B7B459983038DB0BFD90583540DF5E2199C65BAB582C631806B134AF5D6595598C1D415F79E7A485AE6F78447E29FF81D404888D37170E42DD13F4BE90C16C8CB2BDB2EC8358CD9051AC0DA078D057DEFCFE3EDDA783DA92CC208F3B86DF3CF72C0F3C859A693562383EA754EB254630CB5089585030033C66759E87116CF9D681DF365EEE989226C2956A0F78E9928E435172F6E00737418344EEC208BCBB159D8123CB8912F6A337CBEA5B03CE98D3ADC33356C96CD4264CDE5CCDE1EB27F6002C46691D4827BFED9972A6BAC4987C6432BB411C8F9F52A774ADDFF2C7F53E49EDA739B1CF2EF13C75D62C3053189CC0B5C3722ED1C1F3415CCC977BBDA6B2B2F2944EE735D13B8C31955B9CC0DE77D541C9F765B01521970DBDFCF96F80705378524743FB2FE1356C9CFEE33D90B75B3CD0FCDD5920DC5A19C6C369F774EEFFF8F37D4BAC8758DC77E6A4644146EB1557529D35FF1B4B1778814BD65E4C735908E402190C8E1C80FF96D0A626DF599FD6F14EE8E595FFDB3C97C29AA36E14B249FD79AD8D812077F72EE39EDB740C00825404DA06CB27526F49CEB9DF4BFEE3E0622D8B59ED11D6E52DD011825C94A866D064B77D504344997F0A0CFB610E3FA10535127AEC1C67F87751DE7173BFE53E3597177128106CD1F241FC8F27C847E67BEF419D2B77F6B665700BB1C96B5F4775C994DC7341D7F8525C94FC41830C89CEA1B69DD68838113015F509D3A81BFC0C2F4D3145FFB5BA4F852782E43056D1A65D9F4B9F770ED4356785C8A36E33BE3D5AEF7069AC3F4696B4B65C154CA46D2A89BBDD7107416D05FD1649D82B8755B7B80E6824E19DE730F895F6B60D6E63E64D65310B59C2B4453DB2AC03A33BD045E304AA22D87DB448521685475469A25EC0811FC745A2AD52618E8DD9807674F9D26067F7B846BCBBE83A7B3FFCFD13587276F44E4CF8BA0BF1FAFED3DB1FD26801F4C66180EACE5C5B9EEEAA028A994B6F062CEFA91EF118F0B4BA1F99289D7AF83861BA4C736706A90354261EF1ABE9611D4F06996F6243F30CBA6CB2A00571A91D48FC84F66C466B660DF77822CCE184D1CB313ED78AD95EE904CC0DE27B9DA8CE49DDD6615289B2C567B71108045ED5534CCCA6CF46A2DCAF273215D2930D7590BCC975E8A44C726B737219D4CACE47D302F451EF7653A63ED90772FA4691847CCAE68F3581AFCFF08E0E1174769380DF40BF25A1F3FD2D85A88DE3BB702F8147DE60A8CB509970FAB172F2E1682DE3282F03D2E6B8AC6C7B98B98F85F0027CCD47D90F2BABC55AAAA4724A08B365ECE8AA7D8373C76141CC56D64F43379984277BA03B2BC954DE3CF06812FBA6C462E583652049BD37276B0897522E3B0B6BCDF4D8D032E0F6DFA7E6E388A2DBBEFAD7B58E5487321CFE6AFF39C3E7C0FBFD6ED848557C2952CF5FDB03D99E0C2F112077502841D1DD4905B5056E4B202D94B95CDD2D798FDA38CAE3A51E8C14809C7E7CA70316CEBECB25F250B62D54646FA5A62339B2BE43F273488DBD350D334B34AEACBAED01BE424D02C1244C52245B746DA0A6BD092170EF7E4C6B4AA5C449AB11B4879215134E57D06DCFC980F1589ACA45818B186614B0432E698354C4EAA6E5FD8D1CD5C11F0315982EE1522CC6F22D8557CC3DBE64397352EDB9AB247E6F0819E548502A50BB379B5B8432F9B04FDCD7A5EE670694386DAE4DF491EE807D3783E86221408B51B8D3EB8FDD0878E9797F830D95697B9E270422698A77914F59B58E739A171F4BF7BB012572B05CFB66051E32EC69239EEE0248FEEC2AF7B13977B1DC7070043FB099E04C4B2B610EA199922E7D29B766881E8391DD8FD9E26305DBDF963D8929D470157CA6A0216D6E2DA0022796F62C944E98498DF04A252889A93EE6AA55300C1DE6AE5E1C2D499F9EE6C080C5B5A77F4BA851491FECEC268D407299A502F7672CB60E685D50DCB92E309ED85F3088B03E774E964571E846C78CBC75D48BA46598E70D142529D3A04E395E72B7292E961E2BAF0101EA4E54318292CCBE1A499354A8AC656F72AED89B31B54ABA8E149CEC378ACFC6E1D98552323BB7EF439259C0A28AE4EFF217DD09F91C777618917C644B0A265CB6D85001DF9193F89854C1E10C62143BE9ECB3A1EB29E368B6141824C0216EA7DC15F51AE424CCFB6FED85CEB23FD67B338D0D24A9FD70C1610715919F2AC45C6B5D441815555D5D73BE70815AFCC8B0A4C1147D82746DC7E7D866379FE60CB2B444B80E568E0361EF5DD55523D0193BD11C0D90615E5B504F56507499BAE78D117578AEE8EE86BF103D51BABE09B777B9A23F05C0018CFCCF2907874038846A658F3E71356776BB598D1DE55FC9885E49985E12CC7FC8F7BED04C24E48CFE2F671322610B435C09907016E3EBC95EFF5ECD1F4FA8253A1AD5487F1A3663A27958670E5ECB971CDE9B3272ED49C13DEA51161168FA1A9459F97CDB568DD3676DC232C0BDAF07CEBEDFD92E9A152E669B2EFE0029AC28B7B64B792F4EB6FB173BD7F7FE750D78E18FAA1F21D4A87BF888D09D36EAA274D499556BE00361EB83AA870B2BC534575633F7FE93C4BE97A20E58011BA1B39E086C78D54DB1DA94404548472C7E9BE9E86E1273C129E5A909E4D01FBC8A336065CFFA60A233DE79B15EE863FE80F3365E35802C7DC2D1B1C3E3194BA7185F8EC46230F5893BF802A83D045F2E131546B2D096A4D66F144E26A8FA8EB494EEEDFF899F4D4930F0CA5A006FC0C2CF90375DF12B56DA8F7A5A346ECBCFD519DFD01A1BE4FE893B66F099456D7A03B92FAC57C348D4B75C34F341937F1E665354057AD180C1A1A69EA24A410ADEAAD1E3E0F633D6D7A56BB2153D2D29EB029B42C7DE777284DB524F2711A51F57D1BDA7BA587C2EF745EA867CF4BD12F05436F929C77970EEABC849F15AB717EAA5A614D67F7C86E8F5D331CA9EB7DDA110E5852AFA86091C61DC2CE98A232DB9DE4051C92BB23C9B0FB6B46B3995F66562F44E541950E0E9C5EBC86D565C4DCCDAD128F7E7432E9C9B9D38E31309B26F1FED6BC2680190BE12D1C257AD50E41BFF1B3F30DC16A4A870A4D48098102B50F08DF015BD0CE9051B55107774B78B20E2CBE264671662C84CB9F27C43A8BCE6FC06B73316A21FD83D4397F01F600B72A0C0DDA6BC77532FE3744E5563EEF8CB2525DFF2E1A303DDDA6C15B74B534C95FC8C9257930ECC7EF28248E5C8660BC34A87DDA051155302BB1CFA1AEA72830B9982B2527FA33A23F068816CE4C5D04523FC22BF727EA25280AA7A4AFDD48102E78D758EEB3C56F9A17F6C022ACC369E359EC9E4C9280C6AC09D0C73AB1C0BB6C96F11E6074346290FF0452D9A9311733CF5FC698DAD10D2DFF3187C293BA4EEC05F981AA5461A5E098C8EC25C1EC7DF15D2BFF23591E89EB9E49BE297754E2F4130FFACDF00570C617AED7A32EC8C9FF5120B26A8079F1E565FBCC7E695029214CF12417733E2CB1C3FA09174DFBAE59F2C8A2585A68EB9629087B57FEF0B289C9564CF904DFEB9E17F2A6B9AB52AA9CB61913A4E74E4B34E3A8343CCC83379CBCA28F70209BD3110A0EEBB9027109562C9C184B93377191E72FB868C07F0503E5EE2FA8E139F4E3D29B19E3510525131F0C6C25C49BE4F1DFA7AF5FB7B43E49D663A17CF37B826E4CC3104610A4624E802CB80A1D1225738D2324C4B27D3736D92E5698B96B38FA502EA146EB147B4A90DEC15BB7F734A0DBA7BB5D3BC2EF2679214572F795085E83007C90323942914A0139113DD675E9F1CD871D45C92B1A4D8B6A0C0FE4DECFCB4D8EC81206B0F1A23814E2371D4D3F0FC81DEE289DE8322917ACA096DA1BB10CDCF912CEDA633BA0DD4EDF7D30642944A34A51A449936BD0C3A8DC99399E63748E3C4E37F3DE37CE1C41776B6C8B12C644CBC45F3714926DA240DD5A2960F9E93173E25FFAC713AE07827DA98C1C0F1A558C6E26E42C327623EB74A1700F9AF633339AB28889F3D0E624652E008919B4A97D161E5865FCBF2972BE191D1C86C878E88BC3D50D7D0AE9CF37424146306488D6472EA949A0EB5515C1146EDFFC6E4B4C5CD750481B44EB600952BD45F097DBC713E88ACDB1338677E19351CBC4D52F24FE44048EAFA7C70F0128E5F115B5516D04EB34719857BC7CC33F0EB26BD325F62D14412B37C72BC21266E54211D47BEAAB01A87056E9B9AD88CDB8305AB52FD6191666F6D136C273C75C77ACB5FE119DD21C70F4FFAB7B4CEA7B316E2FFCC4C6EB2BD3DF1044A16D3D609BF6E19B01C0CE035175148BA780C6E509E8458329B1AEDF9F02E25BA03E8DE3A5CB1E120453D55A2696A46F5FEAAC49025473ED01478BCBFB0BFAFC969EB37550B022A58B4D13D2F8BA58532DA9BC1186666AB017A7B9246B3FBA75926A4C72B97F8AF4B27946FEDC5A07554CBD1A792D5DBB0591F04B53343CEC496AA418A00A5300421A90394553CD2C3EB70A51B1DCE6C0CC0BFF99DCB8F2771597DC270A1911A23C606DF36A7EFECBE0A00B9FD7FBF6586BA9D770A3C3D6DEAB357E307973E38582CE2A88B868CD291986B97C3040AF48477C586E6AE0BD76A209AA263824344C861B56CED6D4026C9A1DAD1BB9FDB71386B2751255BD6BFF9C02B59DEFADBAF2CB214DB79B6972692DF3A157243431F2BF7FC62048A935D3D392B9A8D52904E66095227C82D625DA9D86E3A9A9F5D881021E8E70187B60040E89200BA35EFEE309F06C8A91C341C46FC3F145B8E125D265EC24AF88EEACCFAB3D227FE9B3EF04B2C661A21FB7721A2CC43DB7BAB904B01BF72DF0602E4C1192AE5BE3EABC5347E2B861D0700AAD018338FF893566CCA1B2BFDCF486F5FF5D54D9DC6B89FCD555AD387A38D175F77AA055A02A6C130885E8F666D9D7145B4D396BC1C2AB9204608C78AEB66DA17EDF8DD395C59DE1FF3F81B5D64663F9322365FBE553E8C1AFFCE2B41C7AA3EF9798E783B80D231CF97F0CB7B5616844D8D0E934180815A8050D697E062566D5AB39B62CF2BA528C302734F945C776473D53E8CBEE5D903E1295D28DE187ABAC4808C748401F61C6D4512B605A646E2E6502FF245DF7A88525F6D9527084E87AE7738BBEBC9CEE78D3FA217BB13F0CAFEF786B448E6C8A28AA6B871ED827D9569E20F60D4475A750BE65A4706598118004200E9151FDE421AAA5C2A3DAE8CAF96E98A8DDCCABDD2176B8DD2BF59B3D0FBA841DDFC74BD7E26B381AD02621D7487040A042B049532196D5EB778A254DD782F87831C81D94A81E909FB74B48680D1C803FAAF96A0CA260A24F21FEDEA3099AC9FCC83E679CEAB2A22AE1F5B0C6850AB0BE7894E886E5422234D05767CDB2A22BBC226A243EBB8C937E4F8212F674A7469FD8022E1F245C1FDD23569A8CA34D8BB5346B95E683A16F9494801957A5166373B9375EDCFE44C1A0947355600F22434C1B9DBE01847D3DDCD9232A56670F00574234BC1643BE0F4D9BFC0950D81C1A31C6B348B2268743076380B1AF99CA3AAEA618A955CBA32E2B97F41EEBA2928EA568DFA5F783B2BE02CBE12FD147A1382E9ABE2F7173D570393C06C0C2F8A4ADF7B7953152D3EBBA7EDEFB50C08FC8D3AE64698619C14F5626AF218301953A69FE005AB930539ED693B091C3D130B29B98CDE85C32649634E457801BE427B712BC7B9E5D4584EBE3E8C8CA800569735B6EDE1EB7B3AF8A033BE5F7B02E0BD70870F766581551B59447EC13618F94CB9C862D924FA688B1C6DEEBEA08802AC66FF7B986BD832C5A8C80E44F56727652AFB0F95FA93DE0F754470046C123AA1CD7BABC5870EA715C82A55E9C4F6E49B56497E4D696B84360348AF11A25DFEC2550705D519FEC9D14428FC5392F7416E8A3441C7E84B676BCC6736FC5092F752461B1461D444141AF8AB986BB23ED75220CD70BBB71E3086F95177943D0299DF3F2E97FE8C2F4F5C19156CF377A9A72BFAFC7AC2CFB4951A93D94B090B2AEFB3E9F6C8F4DE04802D2E13E613C9BC0DF40DF5FA53DD5831B9E4F6B2C997E6BA6FBAE79BA434E371FED374555E80DCEEAC4B1B2CD3A859D99D7F29B8C3DC5799F8AF6F3F1FDD1DE9A5F137D731E3A70CFBA4126AF74FD3D5DAF7A2EDD7E201BC4FEFEACB537AAE25A0B6E165A51FE621F9F4FCAC11B40510CABA0DA82F2BA8EA4B1EE639E3A85B479A40192767CEC3FB686B81EC9F8FC71A320EEA51CD7C2808A7230D8A67BCB29405BFEA4E3E2261525282D3CF17D992D4B162580D5D9672668E941448263D67A7341BE50FCE58E9D0E814C967714553639AD883210F246D1F3CE615297D9D6EF66C814BFB1887AA1DAB86E3CA6CC22269B1102E0AFE85043E77D095D7DA4A8B838710D11190EF49E63459F7E57A9A01E105D99F606E89973B90DB3FE68B710D098682B00420F3D445E300C82A84482B6A3661EE02DC1F0FA65EF3A34BFF686FD65CB001D3F1E76D503CBA6A2A2E8D1D30F11B37212EACFEA0FF20D9CBA11F19D4F37ECCAC792858D620D146C2533DCDF2ED6B7EFF9319CE1C5560158C5797068055A0876A5D080C52EC1E87AE656A82D4A836E39A61F93CF2F72F1C20BF5D48B9878E80D09BAB74E250A6CDC09742DE6F6579E5FD73C4CDDD25A95EBC1719BE024BC8FE7E1AC12E0FAFF46785D1A93811A81F08E8FBB61BA2999C49B3E97A99EB0787B9466BC24B2CC7F1DEBA162A0484E70CAA64AFDC9D3FC4CBA6AAD3009367BBA2831E821057FC8E43C3A4016D93E1115BE137A8A6712DAFD012ADEA1385369676C58B0E219F85235138E74FA73D9393F618733CE4BA8D588B68D7879AA071BE80DFE27996F5957A736833CC24A6F87338C69F3D9F3863A3D7355FF965AE7E9FFD4F349C4CC4F2140F28435FBDE89FD759720BCC1BB5AD3C7E166FB41DBC6998929DD53C1FB3841CC8CA38610E11AAB4654938C890DC9F00BA3C1DC59A9457F13E750933A4258059B7FB4BAA89B0FBB1CB4E84B4593CCC250E5C23D2F6EC66F1B974901B6D75A73D2B067B7BAE3466EBB3F3FB21B589D2EC8ADE45CDD07C8974B6D84186E4117A56BA8C73D3B98838D338D22D169A903759FBFDC653D54DDFB1BFF7FFD3634B802A4036C4FFC819AC29B33F9920F8CD34F8ED11E1D21C624CC826003E2DFAD4624CE79DBE1081BDC01ED527E731240829108CC0D0EF7A20CC528E5EFDD7258B2222B97F31C1E9E4BFC19381B70CE1D9D9AB2D79DB5D6C96F1B91C92BDEAC9A4633655A6C4BB0508EFC53D665828321CB82782C7B0C1B48F5FDA2AF2A83F30ADDFE23E648234B9BCA4AD3D4CD1D6CEBAC39C3B87C4D5C4DC8AAA48E4BC3168D00CC8B6F04F87FA9957A4E2D894346BA9FD4D931C14774FCF7D2610EBFF687F4EE4441C1B8FE84A2C5EAA9CAC071A8061C29B1C48273DC35475BD4C82A72DB9853DBECFB0E7CAF4C2C0654255EB5B994235CAE8E908C671BCF4F2E33CC7A08C7814B8A92D6260E444EAC0B0C983A4241F7D4AF78DEC16C7B078D20F57A48FE100DFC64EFB9AB50BC2ED379B455865EEEE109AFE5B23D563FFDE07858601C8FCAF5FDF2B102E70D830CC74672D92065BF026631DBDD67DAC4380BE6B33D117DBB74FCFC899EC614709D83CF9C821E3E1571EBA0FCFF396E3108F508544CC9022455C79D4D3D72FC6958BFA3F67ECBC4A2C961E4AC882F9B73EFDAF92DE61BD9A829A37012A3ADB642E890807865F8D14A51EAA07AF561F16AA44DE655C4DC87E4B1EF95F3B09F45B43823611A14E345D739FB83136339DA50EE3AA53EBB6B9625BB6248DBF7075F52345D597E7304FB3B9A56115D160B99DE111C78664533E249C4BB7B7335DE6F6BA94A7E30C5A2EF3DC7E4D402F704C6D81D7071BC1D299DDE6109C4458100EC83B3E2517280C8C0B72E43A842ECB54894151882ED95DADFBEFDC45FDCF92017D3271920ACB5165590CDF9F269BCB2C997541C06F3C5BF29F0BBC1E21B6D51FB091CD53C3855EC5A41348F21D4947D876795848DDEAB4240735935AE5333CA0E0B07F1A9BBE5A3B139FA6FE1D0D03EE410917FDA57251FAE218F3E7FFC2C85D15D494F9EC4BF8E4E38A9D0B23EA2D115B2B3BB16CF1A18AC411F38966A20E5332C0A36EB86D5C9761EDEAF6D162C307EA17C3FBBB99D2806DFE161F2D45969E9C680C8FA023EDC41394AA21D787897016F5903E063D99FB70E7620A6BDB0BFF1FAC7D72EC631102F86AFAA6E17817A4B64FC3496B1E5858B96D8150A32915A6EC286296D3DD6D92417FE9DD11B9B685DFBAEB2A4CE9F0797DA68525D56E511DAF946A70E0AE72FEA2757E1DA0DD93E88D911F526760474D61188BE1140E69A281465A88FA82FB14EF92F8EB69C64EE3FA49BBD5C038CF5057A0FE83E46EDFB5C93E04B682159144FFF0C8BDB0B636403B705ED815B4990D2045C40E8A01CDAB2693A966567B1ACDFAFD353A94F7F468E2CBF64026E661E4AA3DBA0F1437E5F865A74F207EE8D8178459BE18FC6D8CDACC857847DA05B9514CAC0BC6C6341A45779F0D349BCB096CCBECAE4859ACA4E15A8D2C3056860434C5601F5D40E1B670E812835B086C5474BC4755D5484E45B98F5DB8EB26FDCBB346A014021C47B04FA38DBD1AE78B413CC6E10940541F1E8B869CD7B64989319E39A9FA5B69FC7356393224FE229ED08518730B2BC435482731A9E10DF998B1C32EBC902B3000E87260FA41792A15BBF635256821F32FCDC7E74400F97FE2224968190218739663D8F3024F16CF8F222B7E170ECA64FF6B743A9B7E425FAAB21C0A8936E50FAEFF402E8D41CCEC1B74904A3AE233DE49EDDCA509291104A5E011E993F0AEB2EF356A3940A6E8D9D17AC7FF4339A8F92142094891C236348695D2565FDFCECD7CDAB3DE13B9B73C8F97608470ED4182A14BD2D8B3673ACA9ACBB37F9D91067F795503B6512D6741F093C5D887D8844F7E1894013E3DCF444A7C8ED79165BEA99D6CB05EC" +} \ No newline at end of file diff --git a/slhdsa/wots.go b/slhdsa/wots.go new file mode 100644 index 0000000..4126ea7 --- /dev/null +++ b/slhdsa/wots.go @@ -0,0 +1,115 @@ +// Copyright 2025 Sun Yimin. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build go1.24 + +package slhdsa + +// Chaining function used in WOTS +// See FIPS 205 Algorithm 5 wots_chain +func (pk *PublicKey) wotsChain(inout []byte, start, steps byte, addr adrsOperations) { + for i := start; i < start+steps; i++ { + addr.setHashAddress(uint32(i)) + pk.h.f(pk, addr, inout, inout) + } +} + +// wotsPkGen generates a WOTS public key. +// See FIPS 205 Algorithm 6 wots_pkGen +func (sk *PrivateKey) wotsPkGen(out []byte, addr adrsOperations) { + skADRS := sk.addressCreator() + skADRS.clone(addr) + skADRS.setTypeAndClear(AddressTypeWOTSPRF) + skADRS.copyKeyPairAddress(addr) + // TODO: use array to avoid heap allocation? + tmpBuf := make([]byte, sk.params.n*sk.params.len) + tmp := tmpBuf + for i := uint32(0); i < sk.params.len; i++ { + skADRS.setChainAddress(i) + sk.h.prf(sk, skADRS, tmp) + addr.setChainAddress(i) + sk.wotsChain(tmp, 0, 15, addr) // w = 16 + tmp = tmp[sk.params.n:] + } + wotspkADRS := sk.addressCreator() + wotspkADRS.clone(addr) + wotspkADRS.setTypeAndClear(AddressTypeWOTSPK) + wotspkADRS.copyKeyPairAddress(addr) + sk.h.t(&sk.PublicKey, wotspkADRS, tmpBuf, out) + clear(tmpBuf) +} + +// wotsSign generates a WOTS signature on an n-byte message. +// See FIPS 205 Algorithm 10 wots_sign +func (sk *PrivateKey) wotsSign(m []byte, adrs adrsOperations, sigWots []byte) { + var msgAndCsum [MAX_WOTS_LEN]byte + // convert message to base w=16 + bytes2nibbles(m, msgAndCsum[:]) + // compute checksum + // checksum = 15 * len1 - sum(msgAndCsum) + var csum uint16 + len1 := sk.params.n * 2 + for i := range len1 { + csum += uint16(msgAndCsum[i]) + } + csum = uint16(15*len1) - csum + msgAndCsum[len1] = byte(csum>>8) & 0x0F + msgAndCsum[len1+1] = byte(csum>>4) & 0x0F + msgAndCsum[len1+2] = byte(csum) & 0x0F + + skADRS := sk.addressCreator() + skADRS.clone(adrs) + skADRS.setTypeAndClear(AddressTypeWOTSPRF) + skADRS.copyKeyPairAddress(adrs) + + for i := range sk.params.len { + skADRS.setChainAddress(i) + sk.h.prf(sk, skADRS, sigWots) + adrs.setChainAddress(i) + sk.wotsChain(sigWots, 0, msgAndCsum[i], adrs) + sigWots = sigWots[sk.params.n:] + } +} + +// wotsPkFromSig computes a WOTS public key from a message and its signature +// See FIPS 205 Algorithm 8 wots_pkFromSig +func (pk *PublicKey) wotsPkFromSig(signature, m []byte, adrs adrsOperations, out []byte) { + var msgAndCsum [MAX_WOTS_LEN]byte + // convert message to base w=16 + bytes2nibbles(m, msgAndCsum[:]) + // compute checksum + // checksum = 15 * len1 - sum(msgAndCsum) + var csum uint16 + len1 := pk.params.n * 2 + for i := range len1 { + csum += uint16(msgAndCsum[i]) + } + csum = uint16(15*len1) - csum + // convert checksum to base w=16 (left shift by 4 first) + msgAndCsum[len1] = byte(csum>>8) & 0x0F + msgAndCsum[len1+1] = byte(csum>>4) & 0x0F + msgAndCsum[len1+2] = byte(csum) & 0x0F + + tmpBuf := make([]byte, pk.params.n*pk.params.len) + copy(tmpBuf, signature) + tmp := tmpBuf + for i := range pk.params.len { + adrs.setChainAddress(i) + pk.wotsChain(tmp, msgAndCsum[i], 15-msgAndCsum[i], adrs) + tmp = tmp[pk.params.n:] + } + wotspkADRS := pk.addressCreator() + wotspkADRS.clone(adrs) + wotspkADRS.setTypeAndClear(AddressTypeWOTSPK) + wotspkADRS.copyKeyPairAddress(adrs) + pk.h.t(pk, wotspkADRS, tmpBuf, out) + clear(tmpBuf) +} + +func bytes2nibbles(in, out []byte) { + for i := range in { + out[i*2] = in[i] >> 4 + out[i*2+1] = in[i] & 0x0F + } +} diff --git a/slhdsa/xmss.go b/slhdsa/xmss.go new file mode 100644 index 0000000..66a6c0a --- /dev/null +++ b/slhdsa/xmss.go @@ -0,0 +1,67 @@ +// Copyright 2025 Sun Yimin. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build go1.24 + +package slhdsa + +// xmssNode computes the root of a Merkle subtree of WOTS public keys. +// See FIPS 205 Algorithm 9 xmss_node +func (sk *PrivateKey) xmssNode(out []byte, i, z uint32, adrs adrsOperations) { + if z == 0 { // height 0 + adrs.setTypeAndClear(AddressTypeWOTSHash) + adrs.setKeyPairAddress(i) + sk.wotsPkGen(out, adrs) + } else { + var lnode, rnode [MAX_N]byte + sk.xmssNode(lnode[:], 2*i, z-1, adrs) + sk.xmssNode(rnode[:], 2*i+1, z-1, adrs) + adrs.setTypeAndClear(AddressTypeTree) + adrs.setTreeHeight(z) + adrs.setTreeIndex(i) + sk.h.h(&sk.PublicKey, adrs, lnode[:], rnode[:], out) + } +} + +// xmssSign generates an XMSS signature. +// See FIPS 205 Algorithm 10 xmss_sign +func (sk *PrivateKey) xmssSign(pkFors []byte, leafIdx uint32, adrs adrsOperations, signature []byte) { + authStart := sk.params.n * sk.params.len + authPath := signature[authStart:] + leafIdxCopy := leafIdx + for j := range sk.params.hm { + sk.xmssNode(authPath, leafIdx^1, j, adrs) + authPath = authPath[sk.params.n:] + leafIdx >>= 1 + } + adrs.setTypeAndClear(AddressTypeWOTSHash) + adrs.setKeyPairAddress(leafIdxCopy) + sk.wotsSign(pkFors, adrs, signature) +} + +// xmssPkFromSig computes an XMSS public key from an XMSS signature. +// See FIPS 205 Algorithm 11 xmss_pkFromSig +func (pk *PublicKey) xmssPkFromSig(leafIdx uint32, signature, m []byte, adrs adrsOperations, out []byte) { + // compute WOTS pk from WOTS signature + adrs.setTypeAndClear(AddressTypeWOTSHash) + adrs.setKeyPairAddress(leafIdx) + pk.wotsPkFromSig(signature, m, adrs, out) + + // compute root from WOTS pk and AUTH + adrs.setTypeAndClear(AddressTypeTree) + signature = signature[pk.params.len*pk.params.n:] // auth path + for k := range pk.params.hm { + adrs.setTreeHeight(k + 1) + if leafIdx&1 == 0 { // even + leafIdx >>= 1 + adrs.setTreeIndex(leafIdx) + pk.h.h(pk, adrs, out, signature, out) + } else { // odd + leafIdx = (leafIdx - 1) >> 1 + adrs.setTreeIndex(leafIdx) + pk.h.h(pk, adrs, signature, out, out) + } + signature = signature[pk.params.n:] + } +}