@@ -7,16 +7,11 @@ package certificates
77
88import (
99 "context"
10- "encoding/base64"
1110 "errors"
1211 "fmt"
13- "sort"
14-
15- "github.com/bsv-blockchain/go-sdk/chainhash"
16- "github.com/bsv-blockchain/go-sdk/overlay"
1712 ec "github.com/bsv-blockchain/go-sdk/primitives/ec"
18- "github.com/bsv-blockchain/go-sdk/util"
1913 "github.com/bsv-blockchain/go-sdk/wallet"
14+ "github.com/bsv-blockchain/go-sdk/wallet/serializer"
2015)
2116
2217var (
@@ -41,7 +36,7 @@ type Certificate struct {
4136 Certifier ec.PublicKey `json:"certifier"`
4237
4338 // The outpoint used to confirm that the certificate has not been revoked
44- RevocationOutpoint * overlay .Outpoint `json:"revocationOutpoint"`
39+ RevocationOutpoint * wallet .Outpoint `json:"revocationOutpoint"`
4540
4641 // All the fields present in the certificate, with field names as keys and encrypted field values as strings
4742 Fields map [wallet.CertificateFieldNameUnder50Bytes ]wallet.StringBase64 `json:"fields"`
@@ -56,7 +51,7 @@ func NewCertificate(
5651 serialNumber wallet.StringBase64 ,
5752 subject ec.PublicKey ,
5853 certifier ec.PublicKey ,
59- revocationOutpoint * overlay .Outpoint ,
54+ revocationOutpoint * wallet .Outpoint ,
6055 fields map [wallet.CertificateFieldNameUnder50Bytes ]wallet.StringBase64 ,
6156 signature []byte ,
6257) * Certificate {
@@ -73,181 +68,37 @@ func NewCertificate(
7368
7469// ToBinary serializes the certificate into binary format
7570func (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 ))
71+ walletCert , err := c .ToWalletCertificate ()
9272 if err != nil {
93- return nil , fmt .Errorf ("invalid serial number encoding : %w" , err )
73+ return nil , fmt .Errorf ("failed to convert certificate to wallet format : %w" , err )
9474 }
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 )))
12175
122- for _ , fieldName := range fieldNames {
123- fieldValue := c .Fields [fieldName ]
124-
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 )
76+ var data []byte
77+ if includeSignature {
78+ data , err = serializer .SerializeCertificate (walletCert )
79+ } else {
80+ data , err = serializer .SerializeCertificateNoSignature (walletCert )
13481 }
135-
136- // Write signature if included
137- if includeSignature && len (c .Signature ) > 0 {
138- writer .WriteBytes (c .Signature )
82+ if err != nil {
83+ return nil , fmt .Errorf ("failed to serialize certificate: %w" , err )
13984 }
14085
141- return writer . Buf , nil
86+ return data , nil
14287}
14388
14489// CertificateFromBinary deserializes a certificate from binary format
14590func 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 )
91+ walletCert , err := serializer .DeserializeCertificate (data )
16492 if err != nil {
165- return nil , fmt .Errorf ("failed to read subject: %w" , err )
166- }
167- subject , err := ec .ParsePubKey (subjectBytes )
168- if err != nil {
169- return nil , fmt .Errorf ("invalid subject public key: %w" , err )
170- }
171-
172- // Read certifier (33 bytes)
173- certifierBytes , err := reader .ReadBytes (33 )
174- 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 )
93+ return nil , fmt .Errorf ("failed to deserialize certificate: %w" , err )
18094 }
18195
182- // Read revocationOutpoint
183- txidBytes , err := reader .ReadBytes (32 )
96+ cert , err := FromWalletCertificate (walletCert )
18497 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 )
190- }
191-
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
98+ return nil , fmt .Errorf ("failed to convert wallet certificate to Certificate: %w" , err )
23499 }
235100
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
101+ return cert , nil
251102}
252103
253104// Verify checks the certificate's validity including signature verification
@@ -354,6 +205,86 @@ func (c *Certificate) Sign(ctx context.Context, certifierWallet *wallet.ProtoWal
354205 return nil
355206}
356207
208+ func (c * Certificate ) ToWalletCertificate () (* wallet.Certificate , error ) {
209+ // Convert StringBase64 type to CertificateType [32]byte
210+ certType , err := c .Type .ToArray ()
211+ if err != nil {
212+ return nil , fmt .Errorf ("invalid certificate type: %w" , err )
213+ }
214+
215+ // Convert StringBase64 serial number to SerialNumber [32]byte
216+ serialNumber , err := c .SerialNumber .ToArray ()
217+ if err != nil {
218+ return nil , fmt .Errorf ("invalid serial number: %w" , err )
219+ }
220+
221+ // Convert Fields map from map[CertificateFieldNameUnder50Bytes]StringBase64 to map[string]string
222+ fields := make (map [string ]string )
223+ for fieldName , fieldValue := range c .Fields {
224+ fields [string (fieldName )] = string (fieldValue )
225+ }
226+
227+ // Convert []byte signature to *ec.Signature
228+ var signature * ec.Signature
229+ if len (c .Signature ) > 0 {
230+ if sig , err := ec .ParseSignature (c .Signature ); err == nil {
231+ signature = sig
232+ }
233+ }
234+
235+ return & wallet.Certificate {
236+ Type : certType ,
237+ SerialNumber : serialNumber ,
238+ Subject : & c .Subject , // Convert value type to pointer
239+ Certifier : & c .Certifier , // Convert value type to pointer
240+ RevocationOutpoint : c .RevocationOutpoint ,
241+ Fields : fields ,
242+ Signature : signature ,
243+ }, nil
244+ }
245+
246+ func FromWalletCertificate (walletCert * wallet.Certificate ) (* Certificate , error ) {
247+ if walletCert == nil {
248+ return nil , fmt .Errorf ("wallet certificate cannot be nil" )
249+ }
250+
251+ // Convert CertificateType [32]byte to StringBase64
252+ certType := wallet .StringBase64FromArray (walletCert .Type )
253+
254+ // Convert SerialNumber [32]byte to StringBase64
255+ serialNumber := wallet .StringBase64FromArray (walletCert .SerialNumber )
256+
257+ // Convert ec.PublicKey to ec.PublicKey
258+ var subject , certifier ec.PublicKey
259+ if walletCert .Subject != nil {
260+ subject = * walletCert .Subject
261+ }
262+ if walletCert .Certifier != nil {
263+ certifier = * walletCert .Certifier
264+ }
265+
266+ // Convert fields map from map[string]string to map[CertificateFieldNameUnder50Bytes]StringBase64
267+ fields := make (map [wallet.CertificateFieldNameUnder50Bytes ]wallet.StringBase64 )
268+ for fieldName , fieldValue := range walletCert .Fields {
269+ fields [wallet .CertificateFieldNameUnder50Bytes (fieldName )] = wallet .StringBase64 (fieldValue )
270+ }
271+
272+ var signature []byte
273+ if walletCert .Signature != nil {
274+ signature = walletCert .Signature .Serialize ()
275+ }
276+
277+ return & Certificate {
278+ Type : certType ,
279+ SerialNumber : serialNumber ,
280+ Subject : subject ,
281+ Certifier : certifier ,
282+ RevocationOutpoint : walletCert .RevocationOutpoint ,
283+ Fields : fields ,
284+ Signature : signature ,
285+ }, nil
286+ }
287+
357288// GetCertificateEncryptionDetails returns protocol ID and key ID for certificate field encryption
358289// For master certificate creation, no serial number is provided because entropy is required
359290// from both the client and the certifier. In this case, the keyID is simply the fieldName.
0 commit comments