Skip to content

Commit 84254d7

Browse files
authored
feat: add secp256k1 support (#3)
Adds a secp256k1 signer and verifier as well as varsig support.
1 parent dd4e204 commit 84254d7

22 files changed

Lines changed: 742 additions & 61 deletions

File tree

did/did.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const KeyPrefix = Prefix + "key:"
1818
const DIDCore = 0x0d1d
1919
const Ed25519 = 0xed
2020
const RSA = 0x1205
21+
const Secp256k1 = 0xe7
2122

2223
var MethodOffset = varint.UvarintSize(uint64(DIDCore))
2324

go.mod

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,27 @@ require (
1111
github.com/multiformats/go-multibase v0.2.0
1212
github.com/multiformats/go-multihash v0.2.3
1313
github.com/multiformats/go-varint v0.0.7
14-
github.com/stretchr/testify v1.10.0
14+
github.com/stretchr/testify v1.11.1
1515
github.com/whyrusleeping/cbor-gen v0.3.1
16+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b
1617
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
1718
)
1819

1920
require (
2021
github.com/davecgh/go-spew v1.1.1 // indirect
2122
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
23+
github.com/kr/pretty v0.3.1 // indirect
2224
github.com/minio/sha256-simd v1.0.0 // indirect
2325
github.com/mr-tron/base58 v1.2.0 // indirect
2426
github.com/multiformats/go-base32 v0.0.3 // indirect
2527
github.com/multiformats/go-base36 v0.1.0 // indirect
2628
github.com/pmezard/go-difflib v1.0.0 // indirect
29+
github.com/rogpeppe/go-internal v1.14.1 // indirect
2730
github.com/spaolacci/murmur3 v1.1.0 // indirect
28-
golang.org/x/crypto v0.31.0 // indirect
29-
golang.org/x/sys v0.28.0 // indirect
31+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
32+
golang.org/x/crypto v0.44.0 // indirect
33+
golang.org/x/sys v0.40.0 // indirect
34+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
3035
gopkg.in/yaml.v3 v3.0.1 // indirect
3136
lukechampine.com/blake3 v1.1.6 // indirect
3237
pitr.ca/jsontokenizer v0.3.0 // indirect

go.sum

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
github.com/alanshaw/dag-json-gen v0.0.4 h1:qoryz04TVH6zu16NRFnzgolzQGaPfTvoIawv/F5rDoY=
22
github.com/alanshaw/dag-json-gen v0.0.4/go.mod h1:rXxWw0SItP9QjxpRMpkju66h0KumF7TPCtvHdOKS5lY=
3+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
34
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
45
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
56
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
@@ -9,6 +10,13 @@ github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN
910
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
1011
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
1112
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
13+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
14+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
15+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
16+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
17+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
18+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
19+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
1220
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
1321
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
1422
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
@@ -25,22 +33,31 @@ github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7B
2533
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
2634
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
2735
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
36+
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
2837
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2938
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
39+
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
40+
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
41+
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
3042
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
3143
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
32-
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
33-
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
44+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
45+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
3446
github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0=
3547
github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
36-
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
37-
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
38-
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
39-
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
48+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA=
49+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
50+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
51+
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
52+
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
53+
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
54+
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
55+
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
4056
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
4157
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
42-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
4358
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
59+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
60+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
4461
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
4562
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
4663
lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c=

principal/ed25519/signer.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import (
88
"github.com/alanshaw/ucantone/did"
99
"github.com/alanshaw/ucantone/principal"
1010
"github.com/alanshaw/ucantone/principal/ed25519/verifier"
11+
"github.com/alanshaw/ucantone/varsig"
1112
"github.com/multiformats/go-multibase"
1213
"github.com/multiformats/go-varint"
1314
)
1415

1516
const Code = 0x1300
1617

17-
const SignatureCode = verifier.SignatureCode
18+
var SignatureAlgorithm = verifier.SignatureAlgorithm
1819

1920
var tagSize = varint.UvarintSize(Code)
2021

@@ -86,8 +87,8 @@ func (s Signer) Code() uint64 {
8687
return Code
8788
}
8889

89-
func (s Signer) SignatureCode() uint64 {
90-
return SignatureCode
90+
func (s Signer) SignatureAlgorithm() varsig.SignatureAlgorithm {
91+
return SignatureAlgorithm
9192
}
9293

9394
func (s Signer) Verifier() principal.Verifier {

principal/ed25519/verifier/verifier.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import (
99
"github.com/alanshaw/ucantone/did"
1010
"github.com/alanshaw/ucantone/principal"
1111
"github.com/alanshaw/ucantone/principal/multiformat"
12-
vsed "github.com/alanshaw/ucantone/varsig/algorithm/ed25519"
12+
varsig_ed25519 "github.com/alanshaw/ucantone/varsig/algorithm/ed25519"
1313
"github.com/multiformats/go-multibase"
1414
"github.com/multiformats/go-varint"
1515
)
1616

1717
const Code = 0xed
18-
const SignatureCode = vsed.Code
18+
19+
var SignatureAlgorithm = varsig_ed25519.New()
1920

2021
var publicTagSize = varint.UvarintSize(Code)
2122

principal/secp256k1/signer.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package secp256k1
2+
3+
import (
4+
"crypto"
5+
"crypto/sha256"
6+
"fmt"
7+
8+
"github.com/alanshaw/ucantone/did"
9+
"github.com/alanshaw/ucantone/principal"
10+
"github.com/alanshaw/ucantone/principal/secp256k1/verifier"
11+
"github.com/alanshaw/ucantone/varsig"
12+
"github.com/multiformats/go-multibase"
13+
"github.com/multiformats/go-varint"
14+
"gitlab.com/yawning/secp256k1-voi/secec"
15+
)
16+
17+
const Code = 0x1301
18+
19+
var SignatureAlgorithm = verifier.SignatureAlgorithm
20+
21+
var tagSize = varint.UvarintSize(Code)
22+
23+
const keySize = 32
24+
25+
var size = tagSize + keySize
26+
27+
func Generate() (Signer, error) {
28+
sk, err := secec.GenerateKey()
29+
if err != nil {
30+
return nil, fmt.Errorf("generating secp256k1 key: %w", err)
31+
}
32+
s := make(Signer, size)
33+
varint.PutUvarint(s, Code)
34+
copy(s[tagSize:], sk.Bytes())
35+
return s, nil
36+
}
37+
38+
// Parse parses a multibase encoded string containing a secp256k1 signer
39+
// multiformat varint (0x1301) + byte secp256k1 raw scalar value.
40+
func Parse(str string) (Signer, error) {
41+
_, bytes, err := multibase.Decode(str)
42+
if err != nil {
43+
return nil, fmt.Errorf("decoding multibase string: %w", err)
44+
}
45+
return Decode(bytes)
46+
}
47+
48+
// Decode decodes a buffer of a secp256k1 signer multiformat varint (0x1301) +
49+
// 32 byte secp256k1 raw scalar value.
50+
func Decode(b []byte) (Signer, error) {
51+
if len(b) != size {
52+
return nil, fmt.Errorf("invalid length: %d wanted: %d", len(b), size)
53+
}
54+
skc, _, err := varint.FromUvarint(b)
55+
if err != nil {
56+
return nil, fmt.Errorf("reading private key uvarint: %w", err)
57+
}
58+
if skc != Code {
59+
return nil, fmt.Errorf("invalid private key codec: 0x%02x, expected: 0x%02x", skc, Code)
60+
}
61+
_, err = secec.NewPrivateKey(b[tagSize:])
62+
if err != nil {
63+
return nil, fmt.Errorf("creating private key: %w", err)
64+
}
65+
s := make(Signer, size)
66+
copy(s, b)
67+
return s, nil
68+
}
69+
70+
// FromRaw takes raw 32 byte scalar value and tags with the secp256k1
71+
// signer multiformat code, returning a secp256k1 signer.
72+
func FromRaw(b []byte) (Signer, error) {
73+
if len(b) != keySize {
74+
return nil, fmt.Errorf("invalid length: %d wanted: %d", len(b), keySize)
75+
}
76+
s := make(Signer, size)
77+
varint.PutUvarint(s, Code)
78+
copy(s[tagSize:], b)
79+
return s, nil
80+
}
81+
82+
type Signer []byte
83+
84+
var _ principal.Signer = (Signer)(nil)
85+
86+
func (s Signer) Code() uint64 {
87+
return Code
88+
}
89+
90+
func (s Signer) SignatureAlgorithm() varsig.SignatureAlgorithm {
91+
return SignatureAlgorithm
92+
}
93+
94+
func (s Signer) Verifier() principal.Verifier {
95+
sk, _ := secec.NewPrivateKey(s[tagSize:])
96+
v, _ := verifier.FromRaw(sk.PublicKey().CompressedBytes())
97+
return v
98+
}
99+
100+
func (s Signer) DID() did.DID {
101+
return s.Verifier().DID()
102+
}
103+
104+
// Bytes returns the private key bytes with multiformat prefix varint.
105+
func (s Signer) Bytes() []byte {
106+
return s
107+
}
108+
109+
// Raw encodes the bytes of the private key without multiformats tags.
110+
func (s Signer) Raw() []byte {
111+
pk := make([]byte, keySize)
112+
copy(pk, s[tagSize:size])
113+
return pk
114+
}
115+
116+
func (s Signer) Sign(msg []byte) []byte {
117+
sk, _ := secec.NewPrivateKey(s[tagSize:])
118+
hash := sha256.New()
119+
hash.Write(msg)
120+
sig, _ := sk.Sign(
121+
secec.RFC6979SHA256(), // for deterministic signatures, per RFC6979
122+
hash.Sum(nil),
123+
&secec.ECDSAOptions{
124+
Encoding: secec.EncodingCompact,
125+
Hash: crypto.SHA256,
126+
SelfVerify: false,
127+
},
128+
)
129+
return sig
130+
}

principal/secp256k1/signer_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package secp256k1_test
2+
3+
import (
4+
"crypto"
5+
"crypto/sha256"
6+
"testing"
7+
8+
secp256k1 "github.com/alanshaw/ucantone/principal/secp256k1"
9+
"github.com/alanshaw/ucantone/principal/signer"
10+
"github.com/stretchr/testify/require"
11+
"gitlab.com/yawning/secp256k1-voi/secec"
12+
)
13+
14+
func TestGenerateEncodeDecode(t *testing.T) {
15+
s0, err := secp256k1.Generate()
16+
require.NoError(t, err)
17+
18+
t.Log(s0.DID().String())
19+
20+
s1, err := secp256k1.Decode(s0.Bytes())
21+
require.NoError(t, err)
22+
23+
t.Log(s1.DID().String())
24+
require.Equal(t, s0.DID(), s1.DID(), "public key mismatch")
25+
}
26+
27+
func TestGenerateFormatParse(t *testing.T) {
28+
s0, err := secp256k1.Generate()
29+
require.NoError(t, err)
30+
31+
t.Log(s0.DID().String())
32+
33+
str := signer.Format(s0)
34+
t.Log(str)
35+
36+
s1, err := secp256k1.Parse(str)
37+
require.NoError(t, err)
38+
39+
t.Log(s1.DID().String())
40+
require.Equal(t, s0.DID(), s1.DID(), "public key mismatch")
41+
}
42+
43+
func TestVerify(t *testing.T) {
44+
s, err := secp256k1.Generate()
45+
require.NoError(t, err)
46+
47+
msg := []byte("testy")
48+
sig := s.Sign(msg)
49+
50+
res := s.Verifier().Verify(msg, sig)
51+
require.True(t, res)
52+
}
53+
54+
func TestSignerRaw(t *testing.T) {
55+
s, err := secp256k1.Generate()
56+
require.NoError(t, err)
57+
58+
msg := []byte{1, 2, 3}
59+
hash := sha256.New()
60+
hash.Write(msg)
61+
raw := s.Raw()
62+
63+
sk, err := secec.NewPrivateKey(raw)
64+
require.NoError(t, err)
65+
66+
sig, err := sk.Sign(
67+
secec.RFC6979SHA256(),
68+
hash.Sum(nil),
69+
&secec.ECDSAOptions{
70+
Encoding: secec.EncodingCompact,
71+
SelfVerify: false,
72+
Hash: crypto.SHA256,
73+
},
74+
)
75+
require.NoError(t, err)
76+
77+
require.Equal(t, s.Sign(msg), sig)
78+
}
79+
80+
func TestFromRaw(t *testing.T) {
81+
t.Run("round trip", func(t *testing.T) {
82+
priv, err := secec.GenerateKey()
83+
require.NoError(t, err)
84+
85+
s, err := secp256k1.FromRaw(priv.Bytes())
86+
require.NoError(t, err)
87+
88+
require.Equal(t, priv.Bytes(), s.Raw())
89+
})
90+
91+
t.Run("invalid length", func(t *testing.T) {
92+
_, err := secp256k1.FromRaw([]byte{})
93+
require.Error(t, err)
94+
require.ErrorContains(t, err, "invalid length")
95+
})
96+
}

0 commit comments

Comments
 (0)