This package demonstrates how to sign and verify AT Protocol lexicon records using cryptographic signatures, following the AT Protocol cryptography specifications.

Adding unit tests and making code more idiomatic 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
···
··· 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
···
··· 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
···
··· 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
··· 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
··· 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
··· 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 - }
···