aboutsummaryrefslogtreecommitdiff
path: root/internal/verify/verifier.go
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-26 15:41:45 -0800
committerFuwn <[email protected]>2026-02-26 15:41:45 -0800
commitfec9114caaa7d274e524793d5eb0cf2ef2c5af11 (patch)
tree0897394ccdfaf6633e1a4ca8eb02bff49bb93c00 /internal/verify/verifier.go
parentfeat: add read-only PLC API compatibility endpoints (diff)
downloadplutia-test-fec9114caaa7d274e524793d5eb0cf2ef2c5af11.tar.xz
plutia-test-fec9114caaa7d274e524793d5eb0cf2ef2c5af11.zip
feat: Apply Iku formatting
Diffstat (limited to 'internal/verify/verifier.go')
-rw-r--r--internal/verify/verifier.go67
1 files changed, 66 insertions, 1 deletions
diff --git a/internal/verify/verifier.go b/internal/verify/verifier.go
index ae648d3..4cb6b38 100644
--- a/internal/verify/verifier.go
+++ b/internal/verify/verifier.go
@@ -13,7 +13,6 @@ import (
"fmt"
"math/big"
"strings"
-
"github.com/Fuwn/plutia/internal/config"
"github.com/Fuwn/plutia/internal/types"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
@@ -38,6 +37,7 @@ func (v *Verifier) ShouldVerify(existing *types.StateV1, seq uint64) bool {
if existing == nil {
return true
}
+
return seq >= existing.LatestOpSeq
default:
return true
@@ -53,36 +53,49 @@ func (v *Verifier) VerifyOperation(op types.ParsedOperation, existing *types.Sta
if op.Prev == "" {
return errors.New("missing prev on non-genesis operation")
}
+
if op.Prev != existing.ChainTipHash {
return fmt.Errorf("prev linkage mismatch: got %s want %s", op.Prev, existing.ChainTipHash)
}
}
sig, ok := findString(op.Payload, "sig", "signature")
+
if !ok || strings.TrimSpace(sig) == "" {
return errors.New("missing signature")
}
+
pubKeys := make([]string, 0, 8)
+
if existing != nil && len(existing.RotationKeys) > 0 {
pubKeys = append(pubKeys, existing.RotationKeys...)
}
+
pubKeys = append(pubKeys, extractPublicKeys(op.Payload)...)
+
if len(pubKeys) == 0 {
return errors.New("missing verification public key")
}
+
sigBytes, err := decodeFlexible(sig)
+
if err != nil {
return fmt.Errorf("decode signature: %w", err)
}
+
payload, err := signaturePayload(op.Payload)
+
if err != nil {
return err
}
+
for _, key := range pubKeys {
vk, err := decodePublicKey(key)
+
if err != nil {
continue
}
+
switch vk.Algo {
case "ed25519":
if ed25519.Verify(vk.Ed25519, payload, sigBytes) {
@@ -98,18 +111,21 @@ func (v *Verifier) VerifyOperation(op types.ParsedOperation, existing *types.Sta
}
}
}
+
return errors.New("signature verification failed")
}
func signaturePayload(m map[string]any) ([]byte, error) {
if raw, ok := findString(m, "sigPayload", "signaturePayload"); ok && raw != "" {
decoded, err := decodeFlexible(raw)
+
if err == nil && json.Valid(decoded) {
return types.CanonicalizeJSON(decoded)
}
}
clone := make(map[string]any, len(m))
+
for k, v := range m {
switch k {
case "sig", "signature", "sigPayload", "signaturePayload":
@@ -118,14 +134,19 @@ func signaturePayload(m map[string]any) ([]byte, error) {
clone[k] = v
}
}
+
encMode, err := cbor.CanonicalEncOptions().EncMode()
+
if err != nil {
return nil, fmt.Errorf("init canonical cbor encoder: %w", err)
}
+
b, err := encMode.Marshal(clone)
+
if err != nil {
return nil, fmt.Errorf("marshal signature payload cbor: %w", err)
}
+
return b, nil
}
@@ -135,6 +156,7 @@ func findString(m map[string]any, keys ...string) (string, bool) {
return v, true
}
}
+
return "", false
}
@@ -143,15 +165,19 @@ func extractPublicKeys(payload map[string]any) []string {
seen := map[string]struct{}{}
add := func(v string) {
v = strings.TrimSpace(v)
+
if v == "" {
return
}
+
if _, ok := seen[v]; ok {
return
}
+
seen[v] = struct{}{}
out = append(out, v)
}
+
if arr, ok := payload["rotationKeys"].([]any); ok {
for _, v := range arr {
if s, ok := v.(string); ok {
@@ -159,19 +185,23 @@ func extractPublicKeys(payload map[string]any) []string {
}
}
}
+
if v, ok := findString(payload, "publicKey", "verificationMethod", "signingKey", "recoveryKey"); ok {
add(v)
}
+
if vm, ok := payload["verificationMethods"].(map[string]any); ok {
if v, ok := vm["atproto"].(string); ok {
add(v)
}
+
for _, anyV := range vm {
if s, ok := anyV.(string); ok {
add(s)
}
}
}
+
return out
}
@@ -185,41 +215,54 @@ type verificationKey struct {
func decodePublicKey(value string) (verificationKey, error) {
if strings.HasPrefix(value, "did:key:") {
mb := strings.TrimPrefix(value, "did:key:")
+
if mb == "" || mb[0] != 'z' {
return verificationKey{}, errors.New("did:key must be multibase base58btc")
}
+
decoded, err := base58.Decode(mb[1:])
+
if err != nil {
return verificationKey{}, fmt.Errorf("decode did:key base58: %w", err)
}
+
if len(decoded) < 3 {
return verificationKey{}, errors.New("invalid did:key length")
}
+
code, n := binary.Uvarint(decoded)
+
if n <= 0 || n >= len(decoded) {
return verificationKey{}, errors.New("invalid did:key multicodec prefix")
}
+
keyBytes := decoded[n:]
+
switch code {
case 0xED:
if len(keyBytes) != ed25519.PublicKeySize {
return verificationKey{}, errors.New("invalid did:key ed25519 length")
}
+
return verificationKey{
Algo: "ed25519",
Ed25519: ed25519.PublicKey(keyBytes),
}, nil
case 0xE7:
pub, err := secp256k1.ParsePubKey(keyBytes)
+
if err != nil {
return verificationKey{}, fmt.Errorf("parse secp256k1 did:key: %w", err)
}
+
return verificationKey{Algo: "secp256k1", Secp256k1: pub}, nil
case 0x1200:
x, y := elliptic.UnmarshalCompressed(elliptic.P256(), keyBytes)
+
if x == nil || y == nil {
return verificationKey{}, errors.New("parse p256 did:key: invalid compressed key")
}
+
return verificationKey{
Algo: "p256",
P256: &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y},
@@ -228,23 +271,30 @@ func decodePublicKey(value string) (verificationKey, error) {
return verificationKey{}, errors.New("unsupported did:key multicodec")
}
}
+
b, err := decodeFlexible(value)
+
if err != nil {
return verificationKey{}, fmt.Errorf("decode public key: %w", err)
}
+
if len(b) == ed25519.PublicKeySize {
return verificationKey{Algo: "ed25519", Ed25519: ed25519.PublicKey(b)}, nil
}
+
pub, err := secp256k1.ParsePubKey(b)
+
if err == nil {
return verificationKey{Algo: "secp256k1", Secp256k1: pub}, nil
}
+
if x, y := elliptic.UnmarshalCompressed(elliptic.P256(), b); x != nil && y != nil {
return verificationKey{
Algo: "p256",
P256: &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y},
}, nil
}
+
return verificationKey{}, fmt.Errorf("invalid public key length/type: %d", len(b))
}
@@ -252,20 +302,28 @@ func verifySecp256k1(pub *secp256k1.PublicKey, payload, sig []byte) bool {
if pub == nil {
return false
}
+
var parsed *secpECDSA.Signature
+
if len(sig) == 64 {
var r, s secp256k1.ModNScalar
+
r.SetByteSlice(sig[:32])
s.SetByteSlice(sig[32:])
+
parsed = secpECDSA.NewSignature(&r, &s)
} else {
der, err := secpECDSA.ParseDERSignature(sig)
+
if err != nil {
return false
}
+
parsed = der
}
+
sum := sha256.Sum256(payload)
+
return parsed.Verify(sum[:], pub)
}
@@ -273,12 +331,16 @@ func verifyP256(pub *ecdsa.PublicKey, payload, sig []byte) bool {
if pub == nil {
return false
}
+
sum := sha256.Sum256(payload)
+
if len(sig) == 64 {
r := new(big.Int).SetBytes(sig[:32])
s := new(big.Int).SetBytes(sig[32:])
+
return ecdsa.Verify(pub, sum[:], r, s)
}
+
return ecdsa.VerifyASN1(pub, sum[:], sig)
}
@@ -286,11 +348,14 @@ func decodeFlexible(v string) ([]byte, error) {
if b, err := base64.RawURLEncoding.DecodeString(v); err == nil {
return b, nil
}
+
if b, err := base64.StdEncoding.DecodeString(v); err == nil {
return b, nil
}
+
if b, err := hex.DecodeString(v); err == nil {
return b, nil
}
+
return nil, errors.New("unsupported encoding")
}