Skip to content

Commit d6fee95

Browse files
authored
Merge pull request #17 from MetaMask/varsig
crypto: integrate varsig
2 parents b520568 + 2f76eaa commit d6fee95

File tree

30 files changed

+451
-123
lines changed

30 files changed

+451
-123
lines changed

.github/workflows/gotest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ jobs:
2424
- name: Run tests
2525
run: go test -v ./...
2626
- name: Check formatted
27-
run: gofmt -l .
27+
run: gofmt -l .

crypto/_testsuite/testsuite.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
mbase "github.com/multiformats/go-multibase"
1010
"github.com/multiformats/go-varint"
1111
"github.com/stretchr/testify/require"
12+
"github.com/ucan-wg/go-varsig"
1213

1314
"github.com/MetaMask/go-did-it/crypto"
1415
)
@@ -194,6 +195,7 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
194195
name string
195196
signer func(msg []byte, opts ...crypto.SigningOption) ([]byte, error)
196197
verifier func(msg []byte, sig []byte, opts ...crypto.SigningOption) bool
198+
varsig func(opts ...crypto.SigningOption) varsig.Varsig
197199
expectedSize int
198200
stats *int
199201
defaultHash crypto.Hash
@@ -210,6 +212,7 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
210212
name: "Bytes signature",
211213
signer: spriv.SignToBytes,
212214
verifier: spub.VerifyBytes,
215+
varsig: spriv.Varsig,
213216
expectedSize: harness.SignatureBytesSize,
214217
stats: &stats.sigRawSize,
215218
defaultHash: harness.DefaultHash,
@@ -227,6 +230,7 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
227230
name: "ASN.1 signature",
228231
signer: spriv.SignToASN1,
229232
verifier: spub.VerifyASN1,
233+
varsig: spriv.Varsig,
230234
stats: &stats.sigAsn1Size,
231235
defaultHash: harness.DefaultHash,
232236
otherHashes: harness.OtherHashes,
@@ -245,6 +249,9 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
245249
sigDefault, err := tc.signer(msg, crypto.WithSigningHash(tc.defaultHash))
246250
require.NoError(t, err)
247251

252+
vsig := tc.varsig()
253+
require.Equal(t, harness.DefaultHash.ToVarsigHash(), vsig.Hash())
254+
248255
if tc.expectedSize > 0 {
249256
require.Equal(t, tc.expectedSize, len(sigNoParams))
250257
}
@@ -253,13 +260,21 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
253260
// signatures might be different (i.e. non-deterministic), but they should verify the same way
254261
valid := tc.verifier(msg, sigNoParams)
255262
require.True(t, valid)
263+
valid = tc.verifier(msg, sigNoParams, crypto.WithVarsig(vsig))
264+
require.True(t, valid)
256265
valid = tc.verifier(msg, sigDefault)
257266
require.True(t, valid)
267+
valid = tc.verifier(msg, sigDefault, crypto.WithVarsig(vsig))
268+
require.True(t, valid)
258269

259270
valid = tc.verifier([]byte("wrong message"), sigNoParams)
260271
require.False(t, valid)
272+
valid = tc.verifier([]byte("wrong message"), sigNoParams, crypto.WithVarsig(vsig))
273+
require.False(t, valid)
261274
valid = tc.verifier([]byte("wrong message"), sigDefault)
262275
require.False(t, valid)
276+
valid = tc.verifier([]byte("wrong message"), sigDefault, crypto.WithVarsig(vsig))
277+
require.False(t, valid)
263278
})
264279
for _, hash := range tc.otherHashes {
265280
t.Run(fmt.Sprintf("%s-%s", tc.name, hash.String()), func(t *testing.T) {
@@ -269,11 +284,18 @@ func TestSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](t *testing.T, har
269284
require.NoError(t, err)
270285
require.NotEmpty(t, sig)
271286

287+
vsig := tc.varsig(crypto.WithSigningHash(hash))
288+
require.Equal(t, hash.ToVarsigHash(), vsig.Hash())
289+
272290
valid := tc.verifier(msg, sig, crypto.WithSigningHash(hash))
273291
require.True(t, valid)
292+
valid = tc.verifier(msg, sig, crypto.WithVarsig(vsig))
293+
require.True(t, valid)
274294

275295
valid = tc.verifier([]byte("wrong message"), sig)
276296
require.False(t, valid)
297+
valid = tc.verifier([]byte("wrong message"), sig, crypto.WithVarsig(vsig))
298+
require.False(t, valid)
277299
})
278300
}
279301
}
@@ -503,6 +525,29 @@ func BenchSuite[PubT crypto.PublicKey, PrivT crypto.PrivateKey](b *testing.B, ha
503525
}
504526
})
505527

528+
b.Run("Verify from varsig signature", func(b *testing.B) {
529+
if !pubImplements[PubT, crypto.PublicKeySigningBytes]() {
530+
b.Skip("Signature to bytes is not implemented")
531+
}
532+
533+
pub, priv, err := harness.GenerateKeyPair()
534+
require.NoError(b, err)
535+
536+
spub := (crypto.PublicKey(pub)).(crypto.PublicKeySigningBytes)
537+
spriv := (crypto.PrivateKey(priv)).(crypto.PrivateKeySigningBytes)
538+
539+
sig, err := spriv.SignToBytes([]byte("message"))
540+
require.NoError(b, err)
541+
vsig := spriv.Varsig()
542+
543+
b.ResetTimer()
544+
b.ReportAllocs()
545+
546+
for i := 0; i < b.N; i++ {
547+
spub.VerifyBytes([]byte("message"), sig, crypto.WithVarsig(vsig))
548+
}
549+
})
550+
506551
b.Run("Sign to ASN.1 signature", func(b *testing.B) {
507552
if !pubImplements[PubT, crypto.PublicKeySigningASN1]() {
508553
b.Skip("Signature to ASN.1 is not implemented")

crypto/ed25519/private.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/pem"
77
"fmt"
88

9+
"github.com/ucan-wg/go-varsig"
910
"golang.org/x/crypto/cryptobyte"
1011

1112
"github.com/MetaMask/go-did-it/crypto"
@@ -21,7 +22,7 @@ type PrivateKey struct {
2122

2223
// PrivateKeyFromBytes converts a serialized private key to a PrivateKey.
2324
// This compact serialization format is the raw key material, without metadata or structure.
24-
// It errors if the slice is not the right size.
25+
// It returns an error if the slice is not the right size.
2526
func PrivateKeyFromBytes(b []byte) (PrivateKey, error) {
2627
if len(b) != PrivateKeyBytesSize {
2728
return PrivateKey{}, fmt.Errorf("invalid ed25519 private key size")
@@ -73,21 +74,32 @@ func (p PrivateKey) Public() crypto.PublicKey {
7374
return PublicKey{k: p.k.Public().(ed25519.PublicKey)}
7475
}
7576

77+
func (p PrivateKey) Varsig(opts ...crypto.SigningOption) varsig.Varsig {
78+
params := crypto.CollectSigningOptions(opts)
79+
return varsig.NewEdDSAVarsig(varsig.CurveEd25519, params.HashOrDefault(crypto.SHA512).ToVarsigHash(), params.PayloadEncoding())
80+
}
81+
7682
func (p PrivateKey) SignToBytes(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
7783
params := crypto.CollectSigningOptions(opts)
78-
if params.Hash != crypto.Hash(0) && params.Hash != crypto.SHA512 {
84+
85+
hash := params.HashOrDefault(crypto.SHA512)
86+
if hash != crypto.SHA512 {
7987
return nil, fmt.Errorf("ed25519 does not support custom hash functions")
8088
}
89+
8190
return ed25519.Sign(p.k, message), nil
8291
}
8392

8493
// SignToASN1 creates a signature with ASN.1 encoding.
8594
// This ASN.1 encoding uses a BIT STRING, which would be correct for an X.509 certificate.
8695
func (p PrivateKey) SignToASN1(message []byte, opts ...crypto.SigningOption) ([]byte, error) {
8796
params := crypto.CollectSigningOptions(opts)
88-
if params.Hash != crypto.Hash(0) && params.Hash != crypto.SHA512 {
97+
98+
hash := params.HashOrDefault(crypto.SHA512)
99+
if hash != crypto.SHA512 {
89100
return nil, fmt.Errorf("ed25519 does not support custom hash functions")
90101
}
102+
91103
sig := ed25519.Sign(p.k, message)
92104
var b cryptobyte.Builder
93105
b.AddASN1BitString(sig)

crypto/ed25519/public.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/pem"
88
"fmt"
99

10+
"github.com/ucan-wg/go-varsig"
1011
"golang.org/x/crypto/cryptobyte"
1112

1213
"github.com/MetaMask/go-did-it/crypto"
@@ -101,21 +102,33 @@ func (p PublicKey) Equal(other crypto.PublicKey) bool {
101102

102103
func (p PublicKey) VerifyBytes(message, signature []byte, opts ...crypto.SigningOption) bool {
103104
params := crypto.CollectSigningOptions(opts)
104-
if params.Hash != crypto.Hash(0) && params.Hash != crypto.SHA512 {
105+
106+
if !params.VarsigMatch(varsig.AlgorithmEdDSA, uint64(varsig.CurveEd25519), 0) {
107+
return false
108+
}
109+
110+
if params.HashOrDefault(crypto.SHA512) != crypto.SHA512 {
105111
// ed25519 does not support custom hash functions
106112
return false
107113
}
114+
108115
return ed25519.Verify(p.k, message, signature)
109116
}
110117

111118
// VerifyASN1 verifies a signature with ASN.1 encoding.
112119
// This ASN.1 encoding uses a BIT STRING, which would be correct for an X.509 certificate.
113120
func (p PublicKey) VerifyASN1(message, signature []byte, opts ...crypto.SigningOption) bool {
114121
params := crypto.CollectSigningOptions(opts)
115-
if params.Hash != crypto.Hash(0) && params.Hash != crypto.SHA512 {
122+
123+
if !params.VarsigMatch(varsig.AlgorithmEdDSA, uint64(varsig.CurveEd25519), 0) {
124+
return false
125+
}
126+
127+
if params.HashOrDefault(crypto.SHA512) != crypto.SHA512 {
116128
// ed25519 does not support custom hash functions
117129
return false
118130
}
131+
119132
var s cryptobyte.String = signature
120133
var bitString asn1.BitString
121134

crypto/hash.go

Lines changed: 104 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,15 @@ import (
55
"hash"
66
"strconv"
77

8+
"github.com/ucan-wg/go-varsig"
89
"golang.org/x/crypto/sha3"
910
)
1011

1112
// As the standard crypto library prohibits from registering additional hash algorithm (like keccak),
1213
// below is essentially an extension of that mechanism to allow it.
1314

14-
func init() {
15-
RegisterHash(KECCAK_256, sha3.NewLegacyKeccak256)
16-
RegisterHash(KECCAK_512, sha3.NewLegacyKeccak512)
17-
}
18-
1915
type Hash uint
2016

21-
// HashFunc simply returns the value of h so that [Hash] implements [SignerOpts].
22-
func (h Hash) HashFunc() Hash {
23-
return h
24-
}
25-
26-
func (h Hash) String() string {
27-
if h < maxStdHash {
28-
return stdcrypto.Hash(h).String()
29-
}
30-
31-
// Extensions
32-
switch h {
33-
case KECCAK_256:
34-
return "Keccak-256"
35-
case KECCAK_512:
36-
return "Keccak-512"
37-
38-
default:
39-
return "unknown hash value " + strconv.Itoa(int(h))
40-
}
41-
}
42-
4317
const (
4418
// From "crypto"
4519
MD4 Hash = 1 + iota // import golang.org/x/crypto/md4
@@ -71,7 +45,20 @@ const (
7145
maxHash
7246
)
7347

74-
var hashes = make([]func() hash.Hash, maxHash-maxStdHash-1)
48+
// HashFunc simply returns the value of h so that [Hash] implements [SignerOpts].
49+
func (h Hash) HashFunc() Hash {
50+
return h
51+
}
52+
53+
func (h Hash) String() string {
54+
if h < maxStdHash {
55+
return stdcrypto.Hash(h).String()
56+
}
57+
if h > maxStdHash && h < maxHash {
58+
return hashNames[h-maxStdHash-1]
59+
}
60+
panic("requested hash #" + strconv.Itoa(int(h)) + " is unavailable")
61+
}
7562

7663
// New returns a new hash.Hash calculating the given hash function. New panics
7764
// if the hash function is not linked into the binary.
@@ -80,23 +67,101 @@ func (h Hash) New() hash.Hash {
8067
return stdcrypto.Hash(h).New()
8168
}
8269
if h > maxStdHash && h < maxHash {
83-
f := hashes[h-maxStdHash-1]
70+
f := hashFns[h-maxStdHash-1]
8471
if f != nil {
8572
return f()
8673
}
8774
}
88-
panic("crypto: requested hash function #" + strconv.Itoa(int(h)) + " is unavailable")
75+
panic("requested hash function #" + strconv.Itoa(int(h)) + " is unavailable")
8976
}
9077

91-
// RegisterHash registers a function that returns a new instance of the given
92-
// hash function. This is intended to be called from the init function in
93-
// packages that implement hash functions.
94-
func RegisterHash(h Hash, f func() hash.Hash) {
95-
if h >= maxHash {
96-
panic("crypto: RegisterHash of unknown hash function")
78+
func (h Hash) ToVarsigHash() varsig.Hash {
79+
if h == MD5SHA1 {
80+
panic("no multihash/multicodec value exists for MD5+SHA1")
81+
}
82+
if h < maxHash {
83+
return hashVarsigs[h]
9784
}
98-
if h <= maxStdHash {
99-
panic("crypto: RegisterHash of standard hash function")
85+
panic("requested hash #" + strconv.Itoa(int(h)) + " is unavailable")
86+
}
87+
88+
func FromVarsigHash(h varsig.Hash) Hash {
89+
switch h {
90+
case varsig.HashMd4:
91+
return MD4
92+
case varsig.HashMd5:
93+
return MD5
94+
case varsig.HashSha1:
95+
return SHA1
96+
case varsig.HashSha2_224:
97+
return SHA224
98+
case varsig.HashSha2_256:
99+
return SHA256
100+
case varsig.HashSha2_384:
101+
return SHA384
102+
case varsig.HashSha2_512:
103+
return SHA512
104+
case varsig.HashRipemd_160:
105+
return RIPEMD160
106+
case varsig.HashSha3_224:
107+
return SHA3_224
108+
case varsig.HashSha3_256:
109+
return SHA3_256
110+
case varsig.HashSha3_384:
111+
return SHA3_384
112+
case varsig.HashSha3_512:
113+
return SHA3_512
114+
case varsig.HashSha512_224:
115+
return SHA512_224
116+
case varsig.HashSha512_256:
117+
return SHA512_256
118+
case varsig.HashBlake2s_256:
119+
return BLAKE2s_256
120+
case varsig.HashBlake2b_256:
121+
return BLAKE2b_256
122+
case varsig.HashBlake2b_384:
123+
return BLAKE2b_384
124+
case varsig.HashBlake2b_512:
125+
return BLAKE2b_512
126+
case varsig.HashKeccak_256:
127+
return KECCAK_256
128+
case varsig.HashKeccak_512:
129+
return KECCAK_512
130+
default:
131+
panic("varsig " + strconv.Itoa(int(h)) + " is not supported")
100132
}
101-
hashes[h-maxStdHash-1] = f
133+
}
134+
135+
var hashNames = []string{
136+
"Keccak-256",
137+
"Keccak-512",
138+
}
139+
var hashFns = []func() hash.Hash{
140+
sha3.NewLegacyKeccak256,
141+
sha3.NewLegacyKeccak512,
142+
}
143+
var hashVarsigs = []varsig.Hash{
144+
0, // undef
145+
varsig.HashMd4,
146+
varsig.HashMd5,
147+
varsig.HashSha1,
148+
varsig.HashSha2_224,
149+
varsig.HashSha2_256,
150+
varsig.HashSha2_384,
151+
varsig.HashSha2_512,
152+
0, // missing MD5SHA1
153+
varsig.HashRipemd_160,
154+
varsig.HashSha3_224,
155+
varsig.HashSha3_256,
156+
varsig.HashSha3_384,
157+
varsig.HashSha3_512,
158+
varsig.HashSha512_224,
159+
varsig.HashSha512_256,
160+
varsig.HashBlake2s_256,
161+
varsig.HashBlake2b_256,
162+
varsig.HashBlake2b_384,
163+
varsig.HashBlake2b_512,
164+
0, // maxStdHash
165+
varsig.HashKeccak_256,
166+
varsig.HashKeccak_512,
102167
}

0 commit comments

Comments
 (0)