1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
package identity
import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"image"
"strings"
"aegis/internal/cryptokit"
)
const VFaceVerificationURL = "https://identicons.virebent.art"
// VFace is the deterministic public profile derived from the three identity
// fields username, email and Ed25519 public key. The private key is never part
// of this structure.
type VFace struct {
Username string
Email string
PublicKey string
Input string
IdentityHash string
PNGHash string
FaceBase64 string
VerificationURL string
}
// GenerateVFace creates the optional 48x48 transparent PNG used by Face and
// the profile preview. The seed is exactly username|email|public-key, matching
// the VFace implementation used by M2Usenet and N2Usenet.
func GenerateVFace(username, email, publicKey string) (VFace, error) {
username = strings.TrimSpace(username)
email = strings.TrimSpace(email)
if username == "" || email == "" {
return VFace{}, errors.New("VFace requires a username and email address")
}
if strings.ContainsAny(username+email, "\r\n|") {
return VFace{}, errors.New("VFace identity fields contain forbidden characters")
}
canonicalKey, err := cryptokit.CanonicalEd25519PublicKey(publicKey)
if err != nil {
return VFace{}, fmt.Errorf("canonicalize VFace public key: %w", err)
}
input := username + "|" + email + "|" + canonicalKey
faceBase64, err := generateFace(input, 48)
if err != nil {
return VFace{}, fmt.Errorf("generate VFace identicon: %w", err)
}
pngBytes, err := base64.StdEncoding.DecodeString(faceBase64)
if err != nil {
return VFace{}, fmt.Errorf("decode VFace PNG: %w", err)
}
if _, err := pngImage(pngBytes, 48); err != nil {
return VFace{}, err
}
identityDigest := sha256.Sum256([]byte(input))
pngDigest := sha256.Sum256(pngBytes)
return VFace{
Username: username,
Email: email,
PublicKey: canonicalKey,
Input: input,
IdentityHash: hex.EncodeToString(identityDigest[:]),
PNGHash: hex.EncodeToString(pngDigest[:]),
FaceBase64: faceBase64,
VerificationURL: VFaceVerificationURL,
}, nil
}
// Headers returns the interoperable VFace headers for an article or email.
// The Face value is folded according to the existing identicon convention.
func (v VFace) Headers() []string {
return []string{
FormatFaceHeader(v.FaceBase64),
"X-VFace-Version: 1",
"X-Ed25519-Pub: " + v.PublicKey,
"Identity-Hash: " + v.IdentityHash,
"X-VFace-Hash: sha256:" + v.IdentityHash,
"X-VFace-PNG-SHA256: " + v.PNGHash,
"X-VFace-Verify: " + v.VerificationURL,
}
}
// DecodeFacePNG decodes a generated VFace image for a GUI preview.
func DecodeFacePNG(value string) (image.Image, error) {
data, err := base64.StdEncoding.DecodeString(strings.TrimSpace(value))
if err != nil {
return nil, fmt.Errorf("decode Face base64: %w", err)
}
return pngImage(data, 48)
}
|