+51
-30
cmd/keys/generate_keys.go
+51
-30
cmd/keys/generate_keys.go
···
17
DIDKey string `json:"didKey"`
18
}
19
20
-
func main() {
21
-
// Generate a new P-256 key pair
22
privateKey, err := crypto.GeneratePrivateKeyP256()
23
if err != nil {
24
-
log.Fatalf("Failed to generate private key: %v", err)
25
}
26
27
-
// Get the public key
28
publicKey, err := privateKey.PublicKey()
29
if err != nil {
30
-
log.Fatalf("Failed to get public key: %v", err)
31
}
32
33
-
// Create a key pair object
34
-
keyPair := KeyPair{
35
PrivateKey: privateKey.Multibase(),
36
PublicKey: publicKey.Multibase(),
37
DIDKey: publicKey.DIDKey(),
38
-
}
39
40
-
// Print the keys
41
-
fmt.Println("Private Key (Multibase):", keyPair.PrivateKey)
42
-
fmt.Println("Public Key (Multibase):", keyPair.PublicKey)
43
-
fmt.Println("Public Key (DID):", keyPair.DIDKey)
44
-
45
-
// Create a directory for the keys if it doesn't exist
46
-
keysDir := "keys"
47
-
if err := os.MkdirAll(keysDir, 0755); err != nil {
48
-
log.Fatalf("Failed to create keys directory: %v", err)
49
}
50
51
-
// Save the key pair to a file
52
keyPairJSON, err := json.MarshalIndent(keyPair, "", " ")
53
if err != nil {
54
-
log.Fatalf("Failed to marshal key pair: %v", err)
55
}
56
57
-
keyPairPath := filepath.Join(keysDir, "keypair.json")
58
if err := os.WriteFile(keyPairPath, keyPairJSON, 0644); err != nil {
59
-
log.Fatalf("Failed to write key pair: %v", err)
60
}
61
62
-
fmt.Printf("Key pair saved to %s\n", keyPairPath)
63
64
// Demonstrate loading a private key from a multibase string
65
loadedPrivateKey, err := crypto.ParsePrivateMultibase(keyPair.PrivateKey)
66
if err != nil {
67
-
log.Fatalf("Failed to parse private key: %v", err)
68
}
69
70
// Get the public key from the loaded private key
71
loadedPublicKey, err := loadedPrivateKey.PublicKey()
72
if err != nil {
73
-
log.Fatalf("Failed to get public key from loaded private key: %v", err)
74
}
75
76
// Verify that the loaded public key matches the original
77
if loadedPublicKey.Multibase() != keyPair.PublicKey {
78
-
log.Fatalf("Loaded public key does not match original")
79
}
80
81
-
fmt.Println("Successfully loaded private key and verified public key")
82
-
83
// Demonstrate loading a public key from a DID key string
84
loadedPublicKeyFromDID, err := crypto.ParsePublicDIDKey(keyPair.DIDKey)
85
if err != nil {
86
-
log.Fatalf("Failed to parse public key from DID: %v", err)
87
}
88
89
// Verify that the loaded public key matches the original
90
if loadedPublicKeyFromDID.Multibase() != keyPair.PublicKey {
91
-
log.Fatalf("Loaded public key from DID does not match original")
92
}
93
94
-
fmt.Println("Successfully loaded public key from DID")
95
}
···
17
DIDKey string `json:"didKey"`
18
}
19
20
+
// GenerateKeyPair creates a new P-256 key pair
21
+
func GenerateKeyPair() (*KeyPair, error) {
22
privateKey, err := crypto.GeneratePrivateKeyP256()
23
if err != nil {
24
+
return nil, fmt.Errorf("failed to generate private key: %v", err)
25
}
26
27
publicKey, err := privateKey.PublicKey()
28
if err != nil {
29
+
return nil, fmt.Errorf("failed to get public key: %v", err)
30
}
31
32
+
return &KeyPair{
33
PrivateKey: privateKey.Multibase(),
34
PublicKey: publicKey.Multibase(),
35
DIDKey: publicKey.DIDKey(),
36
+
}, nil
37
+
}
38
39
+
// SaveKeyPair saves a key pair to a file
40
+
func SaveKeyPair(keyPair *KeyPair, dir string) error {
41
+
if err := os.MkdirAll(dir, 0755); err != nil {
42
+
return fmt.Errorf("failed to create directory: %v", err)
43
}
44
45
keyPairJSON, err := json.MarshalIndent(keyPair, "", " ")
46
if err != nil {
47
+
return fmt.Errorf("failed to marshal key pair: %v", err)
48
}
49
50
+
keyPairPath := filepath.Join(dir, "keypair.json")
51
if err := os.WriteFile(keyPairPath, keyPairJSON, 0644); err != nil {
52
+
return fmt.Errorf("failed to write key pair: %v", err)
53
}
54
55
+
return nil
56
+
}
57
58
+
// LoadAndVerifyKeyPair loads a key pair from a file and verifies it
59
+
func LoadAndVerifyKeyPair(keyPair *KeyPair) error {
60
// Demonstrate loading a private key from a multibase string
61
loadedPrivateKey, err := crypto.ParsePrivateMultibase(keyPair.PrivateKey)
62
if err != nil {
63
+
return fmt.Errorf("failed to parse private key: %v", err)
64
}
65
66
// Get the public key from the loaded private key
67
loadedPublicKey, err := loadedPrivateKey.PublicKey()
68
if err != nil {
69
+
return fmt.Errorf("failed to get public key from loaded private key: %v", err)
70
}
71
72
// Verify that the loaded public key matches the original
73
if loadedPublicKey.Multibase() != keyPair.PublicKey {
74
+
return fmt.Errorf("loaded public key does not match original")
75
}
76
77
// Demonstrate loading a public key from a DID key string
78
loadedPublicKeyFromDID, err := crypto.ParsePublicDIDKey(keyPair.DIDKey)
79
if err != nil {
80
+
return fmt.Errorf("failed to parse public key from DID: %v", err)
81
}
82
83
// Verify that the loaded public key matches the original
84
if loadedPublicKeyFromDID.Multibase() != keyPair.PublicKey {
85
+
return fmt.Errorf("loaded public key from DID does not match original")
86
}
87
88
+
return nil
89
+
}
90
+
91
+
func main() {
92
+
// Generate a new key pair
93
+
keyPair, err := GenerateKeyPair()
94
+
if err != nil {
95
+
log.Fatalf("Failed to generate key pair: %v", err)
96
+
}
97
+
98
+
// Print the keys
99
+
fmt.Println("Private Key (Multibase):", keyPair.PrivateKey)
100
+
fmt.Println("Public Key (Multibase):", keyPair.PublicKey)
101
+
fmt.Println("Public Key (DID):", keyPair.DIDKey)
102
+
103
+
// Save the key pair to a file
104
+
if err := SaveKeyPair(keyPair, "keys"); err != nil {
105
+
log.Fatalf("Failed to save key pair: %v", err)
106
+
}
107
+
108
+
fmt.Printf("Key pair saved to keys/keypair.json\n")
109
+
110
+
// Verify the key pair
111
+
if err := LoadAndVerifyKeyPair(keyPair); err != nil {
112
+
log.Fatalf("Failed to verify key pair: %v", err)
113
+
}
114
+
115
+
fmt.Println("Successfully verified key pair")
116
}
+121
cmd/keys/generate_keys_test.go
+121
cmd/keys/generate_keys_test.go
···
···
1
+
package main
2
+
3
+
import (
4
+
"encoding/json"
5
+
"os"
6
+
"path/filepath"
7
+
"testing"
8
+
)
9
+
10
+
func TestGenerateKeyPair(t *testing.T) {
11
+
keyPair, err := GenerateKeyPair()
12
+
if err != nil {
13
+
t.Fatalf("Failed to generate key pair: %v", err)
14
+
}
15
+
16
+
// Test that all fields are non-empty
17
+
if keyPair.PrivateKey == "" {
18
+
t.Error("PrivateKey is empty")
19
+
}
20
+
if keyPair.PublicKey == "" {
21
+
t.Error("PublicKey is empty")
22
+
}
23
+
if keyPair.DIDKey == "" {
24
+
t.Error("DIDKey is empty")
25
+
}
26
+
27
+
// Test JSON marshaling
28
+
keyPairJSON, err := json.Marshal(keyPair)
29
+
if err != nil {
30
+
t.Fatalf("Failed to marshal key pair: %v", err)
31
+
}
32
+
33
+
// Test JSON unmarshaling
34
+
var unmarshaledKeyPair KeyPair
35
+
if err := json.Unmarshal(keyPairJSON, &unmarshaledKeyPair); err != nil {
36
+
t.Fatalf("Failed to unmarshal key pair: %v", err)
37
+
}
38
+
39
+
// Verify unmarshaled values match original
40
+
if unmarshaledKeyPair.PrivateKey != keyPair.PrivateKey {
41
+
t.Error("Unmarshaled PrivateKey does not match original")
42
+
}
43
+
if unmarshaledKeyPair.PublicKey != keyPair.PublicKey {
44
+
t.Error("Unmarshaled PublicKey does not match original")
45
+
}
46
+
if unmarshaledKeyPair.DIDKey != keyPair.DIDKey {
47
+
t.Error("Unmarshaled DIDKey does not match original")
48
+
}
49
+
}
50
+
51
+
func TestSaveKeyPair(t *testing.T) {
52
+
// Generate key pair
53
+
keyPair, err := GenerateKeyPair()
54
+
if err != nil {
55
+
t.Fatalf("Failed to generate key pair: %v", err)
56
+
}
57
+
58
+
// Create temporary directory for test
59
+
tempDir := t.TempDir()
60
+
keysDir := filepath.Join(tempDir, "keys")
61
+
62
+
// Save key pair
63
+
if err := SaveKeyPair(keyPair, keysDir); err != nil {
64
+
t.Fatalf("Failed to save key pair: %v", err)
65
+
}
66
+
67
+
// Verify file exists
68
+
keyPairPath := filepath.Join(keysDir, "keypair.json")
69
+
if _, err := os.Stat(keyPairPath); os.IsNotExist(err) {
70
+
t.Fatalf("Key pair file was not created")
71
+
}
72
+
73
+
// Read and verify the saved file
74
+
readJSON, err := os.ReadFile(keyPairPath)
75
+
if err != nil {
76
+
t.Fatalf("Failed to read key pair file: %v", err)
77
+
}
78
+
79
+
var readKeyPair KeyPair
80
+
if err := json.Unmarshal(readJSON, &readKeyPair); err != nil {
81
+
t.Fatalf("Failed to unmarshal read key pair: %v", err)
82
+
}
83
+
84
+
// Verify read values match original
85
+
if readKeyPair.PrivateKey != keyPair.PrivateKey {
86
+
t.Error("Read PrivateKey does not match original")
87
+
}
88
+
if readKeyPair.PublicKey != keyPair.PublicKey {
89
+
t.Error("Read PublicKey does not match original")
90
+
}
91
+
if readKeyPair.DIDKey != keyPair.DIDKey {
92
+
t.Error("Read DIDKey does not match original")
93
+
}
94
+
}
95
+
96
+
func TestLoadAndVerifyKeyPair(t *testing.T) {
97
+
// Generate key pair
98
+
keyPair, err := GenerateKeyPair()
99
+
if err != nil {
100
+
t.Fatalf("Failed to generate key pair: %v", err)
101
+
}
102
+
103
+
// Test verification
104
+
if err := LoadAndVerifyKeyPair(keyPair); err != nil {
105
+
t.Fatalf("Failed to verify key pair: %v", err)
106
+
}
107
+
108
+
// Test with invalid private key
109
+
invalidKeyPair := *keyPair
110
+
invalidKeyPair.PrivateKey = "invalid"
111
+
if err := LoadAndVerifyKeyPair(&invalidKeyPair); err == nil {
112
+
t.Error("Expected error with invalid private key, got nil")
113
+
}
114
+
115
+
// Test with invalid DID key
116
+
invalidKeyPair = *keyPair
117
+
invalidKeyPair.DIDKey = "invalid"
118
+
if err := LoadAndVerifyKeyPair(&invalidKeyPair); err == nil {
119
+
t.Error("Expected error with invalid DID key, got nil")
120
+
}
121
+
}
+63
cmd/verify/sign_verify.go
+63
cmd/verify/sign_verify.go
···
···
1
+
package main
2
+
3
+
import (
4
+
"encoding/base64"
5
+
"encoding/json"
6
+
"fmt"
7
+
8
+
"github.com/bluesky-social/indigo/atproto/crypto"
9
+
)
10
+
11
+
// LexiconRecord represents a simple lexicon record that we want to sign
12
+
type LexiconRecord struct {
13
+
Type string `json:"$type"`
14
+
Text string `json:"text"`
15
+
CreatedAt string `json:"createdAt"`
16
+
Author string `json:"author"`
17
+
Signature string `json:"signature,omitempty"`
18
+
}
19
+
20
+
// UnsignedBytes returns the bytes of the record without the signature field
21
+
func (r *LexiconRecord) UnsignedBytes() ([]byte, error) {
22
+
// Create a copy of the record
23
+
recordCopy := *r
24
+
recordCopy.Signature = ""
25
+
26
+
return json.Marshal(&recordCopy)
27
+
}
28
+
29
+
// Sign signs the record using the provided private key
30
+
func (r *LexiconRecord) Sign(privateKey crypto.PrivateKey) error {
31
+
if privateKey == nil {
32
+
return fmt.Errorf("private key cannot be nil")
33
+
}
34
+
bytes, err := r.UnsignedBytes()
35
+
if err != nil {
36
+
return err
37
+
}
38
+
signature, err := privateKey.HashAndSign(bytes)
39
+
if err != nil {
40
+
return err
41
+
}
42
+
r.Signature = base64.RawStdEncoding.EncodeToString(signature)
43
+
return nil
44
+
}
45
+
46
+
// VerifySignature verifies the record's signature using the provided public key
47
+
func (r *LexiconRecord) VerifySignature(publicKey crypto.PublicKey) error {
48
+
if publicKey == nil {
49
+
return fmt.Errorf("public key cannot be nil")
50
+
}
51
+
if r.Signature == "" {
52
+
return fmt.Errorf("cannot verify unsigned record")
53
+
}
54
+
bytes, err := r.UnsignedBytes()
55
+
if err != nil {
56
+
return err
57
+
}
58
+
signature, err := base64.RawStdEncoding.DecodeString(r.Signature)
59
+
if err != nil {
60
+
return err
61
+
}
62
+
return publicKey.HashAndVerify(bytes, signature)
63
+
}
+126
cmd/verify/sign_verify_test.go
+126
cmd/verify/sign_verify_test.go
···
···
1
+
package main
2
+
3
+
import (
4
+
"log"
5
+
"testing"
6
+
7
+
"github.com/bluesky-social/indigo/atproto/crypto"
8
+
"github.com/stretchr/testify/assert"
9
+
)
10
+
11
+
func generateKeys() (crypto.PublicKey, *crypto.PrivateKeyP256) {
12
+
// Generate a new private key (or load an existing one)
13
+
// For demonstration, we'll generate a new P-256 key
14
+
privateKey, err := crypto.GeneratePrivateKeyP256()
15
+
if err != nil {
16
+
log.Fatalf("Failed to generate private key: %v", err)
17
+
}
18
+
19
+
// Get the public key for verification
20
+
publicKey, err := privateKey.PublicKey()
21
+
if err != nil {
22
+
log.Fatalf("Failed to get public key: %v", err)
23
+
}
24
+
25
+
return publicKey, privateKey
26
+
}
27
+
28
+
func TestBrokenRecord(t *testing.T) {
29
+
publicKey, _ := generateKeys()
30
+
31
+
record := LexiconRecord{
32
+
Type: "app.bsky.feed.post",
33
+
Text: "This is a post with a hardcoded signature - DO NOT TRUST THIS!",
34
+
CreatedAt: "2023-04-10T12:00:00Z",
35
+
Author: "did:plc:example123",
36
+
Signature: "THIS_IS_A_TERRIBLE_IDEA",
37
+
}
38
+
39
+
err := record.VerifySignature(publicKey)
40
+
assert.Error(t, err, "verification should fail with invalid signature")
41
+
}
42
+
43
+
func TestLexiconRecordSignAndVerify(t *testing.T) {
44
+
publicKey, privateKey := generateKeys()
45
+
46
+
record := LexiconRecord{
47
+
Type: "app.bsky.feed.post",
48
+
Text: "Hello, AT Protocol! This is a signed lexicon record.",
49
+
CreatedAt: "2023-04-10T12:00:00Z",
50
+
Author: "did:plc:example123",
51
+
}
52
+
53
+
// Sign the record
54
+
signErr := record.Sign(privateKey)
55
+
assert.NoError(t, signErr, "should sign record without error")
56
+
57
+
// Verify the signature
58
+
verifyErr := record.VerifySignature(publicKey)
59
+
assert.NoError(t, verifyErr, "should verify signature without error")
60
+
}
61
+
62
+
// TestUnsignedRecord verifies that attempting to verify an unsigned record returns an error
63
+
func TestUnsignedRecord(t *testing.T) {
64
+
publicKey, _ := generateKeys()
65
+
66
+
record := LexiconRecord{
67
+
Type: "app.bsky.feed.post",
68
+
Text: "This is an unsigned record",
69
+
CreatedAt: "2023-04-10T12:00:00Z",
70
+
Author: "did:plc:example123",
71
+
// Signature is intentionally omitted
72
+
}
73
+
74
+
err := record.VerifySignature(publicKey)
75
+
assert.Error(t, err, "verification should fail for unsigned record")
76
+
assert.Contains(t, err.Error(), "cannot verify unsigned record")
77
+
}
78
+
79
+
// TestInvalidBase64Signature verifies that attempting to verify a record with invalid base64 signature returns an error
80
+
func TestInvalidBase64Signature(t *testing.T) {
81
+
publicKey, _ := generateKeys()
82
+
83
+
record := LexiconRecord{
84
+
Type: "app.bsky.feed.post",
85
+
Text: "This is a record with invalid base64 signature",
86
+
CreatedAt: "2023-04-10T12:00:00Z",
87
+
Author: "did:plc:example123",
88
+
Signature: "not-valid-base64!@#$%", // Invalid base64 string
89
+
}
90
+
91
+
err := record.VerifySignature(publicKey)
92
+
assert.Error(t, err, "verification should fail for invalid base64 signature")
93
+
}
94
+
95
+
// TestSignWithNilPrivateKey verifies that attempting to sign with a nil private key returns an error
96
+
func TestSignWithNilPrivateKey(t *testing.T) {
97
+
record := LexiconRecord{
98
+
Type: "app.bsky.feed.post",
99
+
Text: "This is a record that should fail to sign",
100
+
CreatedAt: "2023-04-10T12:00:00Z",
101
+
Author: "did:plc:example123",
102
+
}
103
+
104
+
err := record.Sign(nil)
105
+
assert.Error(t, err, "signing should fail with nil private key")
106
+
}
107
+
108
+
// TestVerifyWithNilPublicKey verifies that attempting to verify with a nil public key returns an error
109
+
func TestVerifyWithNilPublicKey(t *testing.T) {
110
+
_, privateKey := generateKeys()
111
+
112
+
record := LexiconRecord{
113
+
Type: "app.bsky.feed.post",
114
+
Text: "This is a record that should fail to verify",
115
+
CreatedAt: "2023-04-10T12:00:00Z",
116
+
Author: "did:plc:example123",
117
+
}
118
+
119
+
// First sign the record
120
+
err := record.Sign(privateKey)
121
+
assert.NoError(t, err, "should sign record without error")
122
+
123
+
// Then try to verify with nil public key
124
+
err = record.VerifySignature(nil)
125
+
assert.Error(t, err, "verification should fail with nil public key")
126
+
}
+7
-4
go.mod
+7
-4
go.mod
···
6
7
require (
8
github.com/bluesky-social/indigo v0.0.0-20250410071450-15337ff3600d
9
-
github.com/mr-tron/base58 v1.2.0
10
-
github.com/stretchr/testify v1.9.0
11
-
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b
12
-
golang.org/x/crypto v0.21.0
13
)
14
15
require (
16
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
17
golang.org/x/sys v0.22.0 // indirect
18
)
···
6
7
require (
8
github.com/bluesky-social/indigo v0.0.0-20250410071450-15337ff3600d
9
+
github.com/stretchr/testify v1.10.0
10
)
11
12
require (
13
+
github.com/davecgh/go-spew v1.1.1 // indirect
14
+
github.com/mr-tron/base58 v1.2.0 // indirect
15
+
github.com/pmezard/go-difflib v1.0.0 // indirect
16
+
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
17
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
18
+
golang.org/x/crypto v0.21.0 // indirect
19
golang.org/x/sys v0.22.0 // indirect
20
+
gopkg.in/yaml.v3 v3.0.1 // indirect
21
)
+10
-6
go.sum
+10
-6
go.sum
···
1
github.com/bluesky-social/indigo v0.0.0-20250410071450-15337ff3600d h1:gFSxEaFD8okEyKDhVf9kvBcutaCV7NV9D1fL4atxvmk=
2
github.com/bluesky-social/indigo v0.0.0-20250410071450-15337ff3600d/go.mod h1:yjdhLA1LkK8VDS/WPUoYPo25/Hq/8rX38Ftr67EsqKY=
3
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
4
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
5
-
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
6
-
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
7
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA=
8
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
9
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
10
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
11
-
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
12
-
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
13
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
14
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
15
-
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
16
-
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
17
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
18
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
···
1
github.com/bluesky-social/indigo v0.0.0-20250410071450-15337ff3600d h1:gFSxEaFD8okEyKDhVf9kvBcutaCV7NV9D1fL4atxvmk=
2
github.com/bluesky-social/indigo v0.0.0-20250410071450-15337ff3600d/go.mod h1:yjdhLA1LkK8VDS/WPUoYPo25/Hq/8rX38Ftr67EsqKY=
3
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
6
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
7
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
8
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9
+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
10
+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
11
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA=
12
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
13
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q=
14
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I=
15
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
16
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
17
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
18
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
19
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
20
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
21
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
22
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-127
sign_verify.go
-127
sign_verify.go
···
1
-
package main
2
-
3
-
import (
4
-
"encoding/base64"
5
-
"encoding/json"
6
-
"fmt"
7
-
"log"
8
-
9
-
"github.com/bluesky-social/indigo/atproto/crypto"
10
-
)
11
-
12
-
// LexiconRecord represents a simple lexicon record that we want to sign
13
-
type LexiconRecord struct {
14
-
Type string `json:"$type"`
15
-
Text string `json:"text"`
16
-
CreatedAt string `json:"createdAt"`
17
-
Author string `json:"author"`
18
-
Signature string `json:"signature,omitempty"`
19
-
}
20
-
21
-
// UnsignedBytes returns the bytes of the record without the signature field
22
-
func (r *LexiconRecord) UnsignedBytes() ([]byte, error) {
23
-
// Store the signature temporarily
24
-
sig := r.Signature
25
-
// Clear the signature
26
-
r.Signature = ""
27
-
// Marshal the record
28
-
bytes, err := json.Marshal(r)
29
-
// Restore the signature
30
-
r.Signature = sig
31
-
return bytes, err
32
-
}
33
-
34
-
// Sign signs the record using the provided private key
35
-
func (r *LexiconRecord) Sign(privateKey crypto.PrivateKey) error {
36
-
bytes, err := r.UnsignedBytes()
37
-
if err != nil {
38
-
return err
39
-
}
40
-
signature, err := privateKey.HashAndSign(bytes)
41
-
if err != nil {
42
-
return err
43
-
}
44
-
r.Signature = base64.RawStdEncoding.EncodeToString(signature)
45
-
return nil
46
-
}
47
-
48
-
// VerifySignature verifies the record's signature using the provided public key
49
-
func (r *LexiconRecord) VerifySignature(publicKey crypto.PublicKey) error {
50
-
if r.Signature == "" {
51
-
return fmt.Errorf("cannot verify unsigned record")
52
-
}
53
-
bytes, err := r.UnsignedBytes()
54
-
if err != nil {
55
-
return err
56
-
}
57
-
signature, err := base64.RawStdEncoding.DecodeString(r.Signature)
58
-
if err != nil {
59
-
return err
60
-
}
61
-
return publicKey.HashAndVerify(bytes, signature)
62
-
}
63
-
64
-
func testWorkingRecord(privateKey crypto.PrivateKey, publicKey crypto.PublicKey) {
65
-
fmt.Println("\n=== Testing Working Record ===")
66
-
67
-
// Create a lexicon record
68
-
record := LexiconRecord{
69
-
Type: "app.bsky.feed.post",
70
-
Text: "Hello, AT Protocol! This is a signed lexicon record.",
71
-
CreatedAt: "2023-04-10T12:00:00Z",
72
-
Author: "did:plc:example123",
73
-
}
74
-
75
-
// Sign the record
76
-
if err := record.Sign(privateKey); err != nil {
77
-
log.Fatalf("Failed to sign record: %v", err)
78
-
}
79
-
80
-
// Verify the signature
81
-
if err := record.VerifySignature(publicKey); err != nil {
82
-
log.Fatalf("Signature verification failed: %v", err)
83
-
}
84
-
85
-
fmt.Println("Successfully verified signature!")
86
-
}
87
-
88
-
func testBrokenRecord(publicKey crypto.PublicKey) {
89
-
fmt.Println("\n=== Testing Broken Record ===")
90
-
91
-
// Create a record with a hardcoded signature
92
-
record := LexiconRecord{
93
-
Type: "app.bsky.feed.post",
94
-
Text: "This is a post with a hardcoded signature - DO NOT TRUST THIS!",
95
-
Signature: "THIS_IS_A_TERRIBLE_IDEA",
96
-
}
97
-
98
-
// Try to verify the record with the hardcoded signature
99
-
if err := record.VerifySignature(publicKey); err != nil {
100
-
fmt.Printf("Expected verification failure: %v\n", err)
101
-
} else {
102
-
fmt.Println("WARNING: Hardcoded signature was verified!")
103
-
}
104
-
}
105
-
106
-
func main() {
107
-
// Generate a new private key (or load an existing one)
108
-
// For demonstration, we'll generate a new P-256 key
109
-
privateKey, err := crypto.GeneratePrivateKeyP256()
110
-
if err != nil {
111
-
log.Fatalf("Failed to generate private key: %v", err)
112
-
}
113
-
114
-
// Get the public key for verification
115
-
publicKey, err := privateKey.PublicKey()
116
-
if err != nil {
117
-
log.Fatalf("Failed to get public key: %v", err)
118
-
}
119
-
120
-
// Print the public key in DID format for reference
121
-
fmt.Printf("Public Key (DID): %s\n", publicKey.DIDKey())
122
-
fmt.Printf("Public Key (Multibase): %s\n", publicKey.Multibase())
123
-
124
-
// Run the tests
125
-
testWorkingRecord(privateKey, publicKey)
126
-
testBrokenRecord(publicKey)
127
-
}
···