Skip to content

Commit e635525

Browse files
committed
Unify certificate serialization. Common conversion between certificate types. Fix outpoint encoding.
1 parent 53e9120 commit e635525

17 files changed

+411
-345
lines changed

auth/certificates/certificate.go

Lines changed: 114 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,12 @@ package certificates
77

88
import (
99
"context"
10-
"encoding/base64"
1110
"errors"
1211
"fmt"
13-
"sort"
14-
15-
"github.com/bsv-blockchain/go-sdk/chainhash"
1612
"github.com/bsv-blockchain/go-sdk/overlay"
1713
ec "github.com/bsv-blockchain/go-sdk/primitives/ec"
18-
"github.com/bsv-blockchain/go-sdk/util"
1914
"github.com/bsv-blockchain/go-sdk/wallet"
15+
"github.com/bsv-blockchain/go-sdk/wallet/serializer"
2016
)
2117

2218
var (
@@ -73,181 +69,37 @@ func NewCertificate(
7369

7470
// ToBinary serializes the certificate into binary format
7571
func (c *Certificate) ToBinary(includeSignature bool) ([]byte, error) {
76-
// ensure parameters are valid
77-
if c.Type == "" || c.SerialNumber == "" || c.RevocationOutpoint == nil || c.Fields == nil {
78-
return nil, ErrInvalidCertificate
79-
}
80-
81-
writer := util.NewWriter()
82-
83-
// Write type (StringBase64, 32 bytes)
84-
typeBytes, err := base64.StdEncoding.DecodeString(string(c.Type))
85-
if err != nil {
86-
return nil, fmt.Errorf("invalid type encoding: %w", err)
87-
}
88-
writer.WriteBytes(typeBytes)
89-
90-
// Write serialNumber (StringBase64, 32 bytes)
91-
serialNumberBytes, err := base64.StdEncoding.DecodeString(string(c.SerialNumber))
72+
walletCert, err := c.ToWalletCertificate()
9273
if err != nil {
93-
return nil, fmt.Errorf("invalid serial number encoding: %w", err)
74+
return nil, fmt.Errorf("failed to convert certificate to wallet format: %w", err)
9475
}
95-
writer.WriteBytes(serialNumberBytes)
96-
97-
// Write subject (33 bytes compressed public key)
98-
subjectBytes := c.Subject.Compressed()
99-
writer.WriteBytes(subjectBytes)
100-
101-
// Write certifier (33 bytes compressed public key)
102-
certifierBytes := c.Certifier.Compressed()
103-
writer.WriteBytes(certifierBytes)
104-
105-
// Write revocationOutpoint (TXID + OutputIndex)
106-
writer.WriteBytes(c.RevocationOutpoint.Txid[:])
107-
writer.WriteVarInt(uint64(c.RevocationOutpoint.OutputIndex))
108-
109-
// Write fields
110-
// Sort field names lexicographically
111-
fieldNames := make([]wallet.CertificateFieldNameUnder50Bytes, 0, len(c.Fields))
112-
for fieldName := range c.Fields {
113-
fieldNames = append(fieldNames, fieldName)
114-
}
115-
sort.Slice(fieldNames, func(i, j int) bool {
116-
return fieldNames[i] < fieldNames[j]
117-
})
118-
119-
// Write field count as varint
120-
writer.WriteVarInt(uint64(len(fieldNames)))
121-
122-
for _, fieldName := range fieldNames {
123-
fieldValue := c.Fields[fieldName]
12476

125-
// Field name length + name
126-
fieldNameBytes := []byte(fieldName)
127-
writer.WriteVarInt(uint64(len(fieldNameBytes)))
128-
writer.WriteBytes(fieldNameBytes)
129-
130-
// Field value length + value
131-
fieldValueBytes := []byte(fieldValue)
132-
writer.WriteVarInt(uint64(len(fieldValueBytes)))
133-
writer.WriteBytes(fieldValueBytes)
77+
var data []byte
78+
if includeSignature {
79+
data, err = serializer.SerializeCertificate(walletCert)
80+
} else {
81+
data, err = serializer.SerializeCertificateNoSignature(walletCert)
13482
}
135-
136-
// Write signature if included
137-
if includeSignature && len(c.Signature) > 0 {
138-
writer.WriteBytes(c.Signature)
83+
if err != nil {
84+
return nil, fmt.Errorf("failed to serialize certificate: %w", err)
13985
}
14086

141-
return writer.Buf, nil
87+
return data, nil
14288
}
14389

14490
// CertificateFromBinary deserializes a certificate from binary format
14591
func CertificateFromBinary(data []byte) (*Certificate, error) {
146-
reader := util.NewReader(data)
147-
148-
// Read type (32 bytes)
149-
typeBytes, err := reader.ReadBytes(32)
150-
if err != nil {
151-
return nil, fmt.Errorf("failed to read type: %w", err)
152-
}
153-
typeStr := base64.StdEncoding.EncodeToString(typeBytes)
154-
155-
// Read serialNumber (32 bytes)
156-
serialNumberBytes, err := reader.ReadBytes(32)
157-
if err != nil {
158-
return nil, fmt.Errorf("failed to read serial number: %w", err)
159-
}
160-
serialNumber := base64.StdEncoding.EncodeToString(serialNumberBytes)
161-
162-
// Read subject (33 bytes)
163-
subjectBytes, err := reader.ReadBytes(33)
164-
if err != nil {
165-
return nil, fmt.Errorf("failed to read subject: %w", err)
166-
}
167-
subject, err := ec.ParsePubKey(subjectBytes)
92+
walletCert, err := serializer.DeserializeCertificate(data)
16893
if err != nil {
169-
return nil, fmt.Errorf("invalid subject public key: %w", err)
94+
return nil, fmt.Errorf("failed to deserialize certificate: %w", err)
17095
}
17196

172-
// Read certifier (33 bytes)
173-
certifierBytes, err := reader.ReadBytes(33)
97+
cert, err := FromWalletCertificate(walletCert)
17498
if err != nil {
175-
return nil, fmt.Errorf("failed to read certifier: %w", err)
176-
}
177-
certifier, err := ec.ParsePubKey(certifierBytes)
178-
if err != nil {
179-
return nil, fmt.Errorf("invalid certifier public key: %w", err)
180-
}
181-
182-
// Read revocationOutpoint
183-
txidBytes, err := reader.ReadBytes(32)
184-
if err != nil {
185-
return nil, fmt.Errorf("failed to read txid: %w", err)
186-
}
187-
outputIndex, err := reader.ReadVarInt()
188-
if err != nil {
189-
return nil, fmt.Errorf("failed to read output index: %w", err)
99+
return nil, fmt.Errorf("failed to convert wallet certificate to Certificate: %w", err)
190100
}
191101

192-
// Create revocation outpoint
193-
revocationOutpoint := &overlay.Outpoint{
194-
Txid: chainhash.Hash(txidBytes),
195-
OutputIndex: uint32(outputIndex),
196-
}
197-
198-
// Read field count (varint)
199-
fieldCount, err := reader.ReadVarInt()
200-
if err != nil {
201-
return nil, fmt.Errorf("failed to read field count: %w", err)
202-
}
203-
204-
// Read fields
205-
fields := make(map[wallet.CertificateFieldNameUnder50Bytes]wallet.StringBase64)
206-
for i := uint64(0); i < fieldCount; i++ {
207-
// Field name length (varint)
208-
fieldNameLength, err := reader.ReadVarInt()
209-
if err != nil {
210-
return nil, fmt.Errorf("failed to read field name length: %w", err)
211-
}
212-
213-
// Field name
214-
fieldNameBytes, err := reader.ReadBytes(int(fieldNameLength))
215-
if err != nil {
216-
return nil, fmt.Errorf("failed to read field name: %w", err)
217-
}
218-
fieldName := wallet.CertificateFieldNameUnder50Bytes(string(fieldNameBytes))
219-
220-
// Field value length (varint)
221-
fieldValueLength, err := reader.ReadVarInt()
222-
if err != nil {
223-
return nil, fmt.Errorf("failed to read field value length: %w", err)
224-
}
225-
226-
// Field value
227-
fieldValueBytes, err := reader.ReadBytes(int(fieldValueLength))
228-
if err != nil {
229-
return nil, fmt.Errorf("failed to read field value: %w", err)
230-
}
231-
fieldValue := wallet.StringBase64(string(fieldValueBytes))
232-
233-
fields[fieldName] = fieldValue
234-
}
235-
236-
// Read signature if present
237-
var signature []byte
238-
if reader.Pos < len(data) {
239-
signature = reader.ReadRemaining()
240-
}
241-
242-
return &Certificate{
243-
Type: wallet.StringBase64(typeStr),
244-
SerialNumber: wallet.StringBase64(serialNumber),
245-
Subject: *subject,
246-
Certifier: *certifier,
247-
RevocationOutpoint: revocationOutpoint,
248-
Fields: fields,
249-
Signature: signature,
250-
}, nil
102+
return cert, nil
251103
}
252104

253105
// Verify checks the certificate's validity including signature verification
@@ -354,6 +206,104 @@ func (c *Certificate) Sign(ctx context.Context, certifierWallet *wallet.ProtoWal
354206
return nil
355207
}
356208

209+
func (c *Certificate) ToWalletCertificate() (*wallet.Certificate, error) {
210+
// Convert StringBase64 type to CertificateType [32]byte
211+
certType, err := c.Type.ToArray()
212+
if err != nil {
213+
return nil, fmt.Errorf("invalid certificate type: %w", err)
214+
}
215+
216+
// Convert StringBase64 serial number to SerialNumber [32]byte
217+
serialNumber, err := c.SerialNumber.ToArray()
218+
if err != nil {
219+
return nil, fmt.Errorf("invalid serial number: %w", err)
220+
}
221+
222+
// Convert overlay.Outpoint to wallet.Outpoint
223+
var revocationOutpoint *wallet.Outpoint
224+
if c.RevocationOutpoint != nil {
225+
revocationOutpoint = &wallet.Outpoint{
226+
Txid: c.RevocationOutpoint.Txid,
227+
Index: c.RevocationOutpoint.OutputIndex,
228+
}
229+
}
230+
231+
// Convert Fields map from map[CertificateFieldNameUnder50Bytes]StringBase64 to map[string]string
232+
fields := make(map[string]string)
233+
for fieldName, fieldValue := range c.Fields {
234+
fields[string(fieldName)] = string(fieldValue)
235+
}
236+
237+
// Convert []byte signature to *ec.Signature
238+
var signature *ec.Signature
239+
if len(c.Signature) > 0 {
240+
if sig, err := ec.ParseSignature(c.Signature); err == nil {
241+
signature = sig
242+
}
243+
}
244+
245+
return &wallet.Certificate{
246+
Type: certType,
247+
SerialNumber: serialNumber,
248+
Subject: &c.Subject, // Convert value type to pointer
249+
Certifier: &c.Certifier, // Convert value type to pointer
250+
RevocationOutpoint: revocationOutpoint,
251+
Fields: fields,
252+
Signature: signature,
253+
}, nil
254+
}
255+
256+
func FromWalletCertificate(walletCert *wallet.Certificate) (*Certificate, error) {
257+
if walletCert == nil {
258+
return nil, fmt.Errorf("wallet certificate cannot be nil")
259+
}
260+
261+
// Convert CertificateType [32]byte to StringBase64
262+
certType := wallet.StringBase64FromArray(walletCert.Type)
263+
264+
// Convert SerialNumber [32]byte to StringBase64
265+
serialNumber := wallet.StringBase64FromArray(walletCert.SerialNumber)
266+
267+
// Convert ec.PublicKey to ec.PublicKey
268+
var subject, certifier ec.PublicKey
269+
if walletCert.Subject != nil {
270+
subject = *walletCert.Subject
271+
}
272+
if walletCert.Certifier != nil {
273+
certifier = *walletCert.Certifier
274+
}
275+
276+
// Convert wallet.Outpoint to overlay.Outpoint
277+
var revocationOutpoint *overlay.Outpoint
278+
if walletCert.RevocationOutpoint != nil {
279+
revocationOutpoint = &overlay.Outpoint{
280+
Txid: walletCert.RevocationOutpoint.Txid,
281+
OutputIndex: walletCert.RevocationOutpoint.Index,
282+
}
283+
}
284+
285+
// Convert fields map from map[string]string to map[CertificateFieldNameUnder50Bytes]StringBase64
286+
fields := make(map[wallet.CertificateFieldNameUnder50Bytes]wallet.StringBase64)
287+
for fieldName, fieldValue := range walletCert.Fields {
288+
fields[wallet.CertificateFieldNameUnder50Bytes(fieldName)] = wallet.StringBase64(fieldValue)
289+
}
290+
291+
var signature []byte
292+
if walletCert.Signature != nil {
293+
signature = walletCert.Signature.Serialize()
294+
}
295+
296+
return &Certificate{
297+
Type: certType,
298+
SerialNumber: serialNumber,
299+
Subject: subject,
300+
Certifier: certifier,
301+
RevocationOutpoint: revocationOutpoint,
302+
Fields: fields,
303+
Signature: signature,
304+
}, nil
305+
}
306+
357307
// GetCertificateEncryptionDetails returns protocol ID and key ID for certificate field encryption
358308
// For master certificate creation, no serial number is provided because entropy is required
359309
// from both the client and the certifier. In this case, the keyID is simply the fieldName.

0 commit comments

Comments
 (0)