@@ -7,16 +7,12 @@ 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 "
13+ "github.com/bsv-blockchain/go-sdk/transaction "
1914 "github.com/bsv-blockchain/go-sdk/wallet"
15+ "github.com/bsv-blockchain/go-sdk/wallet/serializer"
2016)
2117
2218var (
@@ -29,10 +25,10 @@ var (
2925// It provides methods for serialization, deserialization, signing, and verifying certificates.
3026type Certificate struct {
3127 // Type identifier for the certificate, base64 encoded string, 32 bytes
32- Type wallet.Base64String `json:"type"`
28+ Type wallet.StringBase64 `json:"type"`
3329
3430 // Unique serial number of the certificate, base64 encoded string, 32 bytes
35- SerialNumber wallet.Base64String `json:"serialNumber"`
31+ SerialNumber wallet.StringBase64 `json:"serialNumber"`
3632
3733 // The public key belonging to the certificate's subject
3834 Subject ec.PublicKey `json:"subject"`
@@ -41,23 +37,23 @@ type Certificate struct {
4137 Certifier ec.PublicKey `json:"certifier"`
4238
4339 // The outpoint used to confirm that the certificate has not been revoked
44- RevocationOutpoint * overlay .Outpoint `json:"revocationOutpoint"`
40+ RevocationOutpoint * transaction .Outpoint `json:"revocationOutpoint"`
4541
4642 // All the fields present in the certificate, with field names as keys and encrypted field values as strings
47- Fields map [wallet.CertificateFieldNameUnder50Bytes ]wallet.Base64String `json:"fields"`
43+ Fields map [wallet.CertificateFieldNameUnder50Bytes ]wallet.StringBase64 `json:"fields"`
4844
4945 // Certificate signature by the certifier's private key
5046 Signature []byte `json:"signature,omitempty"`
5147}
5248
5349// NewCertificate creates a new certificate with the given fields
5450func NewCertificate (
55- certType wallet.Base64String ,
56- serialNumber wallet.Base64String ,
51+ certType wallet.StringBase64 ,
52+ serialNumber wallet.StringBase64 ,
5753 subject ec.PublicKey ,
5854 certifier ec.PublicKey ,
59- revocationOutpoint * overlay .Outpoint ,
60- fields map [wallet.CertificateFieldNameUnder50Bytes ]wallet.Base64String ,
55+ revocationOutpoint * transaction .Outpoint ,
56+ fields map [wallet.CertificateFieldNameUnder50Bytes ]wallet.StringBase64 ,
6157 signature []byte ,
6258) * Certificate {
6359 return & Certificate {
@@ -73,181 +69,37 @@ func NewCertificate(
7369
7470// ToBinary serializes the certificate into binary format
7571func (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 (Base64String, 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 (Base64String, 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 )))
12176
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 )
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
14591func CertificateFromBinary (data []byte ) (* Certificate , error ) {
146- reader := util .NewReader (data )
147-
148- // Read type (32 bytes)
149- typeBytes , err := reader .ReadBytes (32 )
92+ walletCert , err := serializer .DeserializeCertificate (data )
15093 if err != nil {
151- return nil , fmt .Errorf ("failed to read type : %w" , err )
94+ return nil , fmt .Errorf ("failed to deserialize certificate : %w" , err )
15295 }
153- typeStr := base64 .StdEncoding .EncodeToString (typeBytes )
15496
155- // Read serialNumber (32 bytes)
156- serialNumberBytes , err := reader .ReadBytes (32 )
97+ cert , err := FromWalletCertificate (walletCert )
15798 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 )
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 )
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 )
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.Base64String )
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 .Base64String (string (fieldValueBytes ))
232-
233- fields [fieldName ] = fieldValue
99+ return nil , fmt .Errorf ("failed to convert wallet certificate to Certificate: %w" , err )
234100 }
235101
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 .Base64String (typeStr ),
244- SerialNumber : wallet .Base64String (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
@@ -271,7 +123,7 @@ func (c *Certificate) Verify(ctx context.Context) error {
271123 }
272124
273125 // Parse the signature
274- sig , err := ec .ParseSignature (c .Signature )
126+ signature , err := ec .ParseSignature (c .Signature )
275127 if err != nil {
276128 return fmt .Errorf ("failed to parse signature: %w" , err )
277129 }
@@ -290,7 +142,7 @@ func (c *Certificate) Verify(ctx context.Context) error {
290142 },
291143 },
292144 Data : data ,
293- Signature : * sig ,
145+ Signature : signature ,
294146 }
295147
296148 verifyResult , err := verifier .VerifySignature (ctx , verifyArgs , "" )
@@ -354,6 +206,86 @@ 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 Fields map from map[CertificateFieldNameUnder50Bytes]StringBase64 to map[string]string
223+ fields := make (map [string ]string )
224+ for fieldName , fieldValue := range c .Fields {
225+ fields [string (fieldName )] = string (fieldValue )
226+ }
227+
228+ // Convert []byte signature to *ec.Signature
229+ var signature * ec.Signature
230+ if len (c .Signature ) > 0 {
231+ if sig , err := ec .ParseSignature (c .Signature ); err == nil {
232+ signature = sig
233+ }
234+ }
235+
236+ return & wallet.Certificate {
237+ Type : certType ,
238+ SerialNumber : serialNumber ,
239+ Subject : & c .Subject , // Convert value type to pointer
240+ Certifier : & c .Certifier , // Convert value type to pointer
241+ RevocationOutpoint : c .RevocationOutpoint ,
242+ Fields : fields ,
243+ Signature : signature ,
244+ }, nil
245+ }
246+
247+ func FromWalletCertificate (walletCert * wallet.Certificate ) (* Certificate , error ) {
248+ if walletCert == nil {
249+ return nil , fmt .Errorf ("wallet certificate cannot be nil" )
250+ }
251+
252+ // Convert CertificateType [32]byte to StringBase64
253+ certType := wallet .StringBase64FromArray (walletCert .Type )
254+
255+ // Convert SerialNumber [32]byte to StringBase64
256+ serialNumber := wallet .StringBase64FromArray (walletCert .SerialNumber )
257+
258+ // Convert ec.PublicKey to ec.PublicKey
259+ var subject , certifier ec.PublicKey
260+ if walletCert .Subject != nil {
261+ subject = * walletCert .Subject
262+ }
263+ if walletCert .Certifier != nil {
264+ certifier = * walletCert .Certifier
265+ }
266+
267+ // Convert fields map from map[string]string to map[CertificateFieldNameUnder50Bytes]StringBase64
268+ fields := make (map [wallet.CertificateFieldNameUnder50Bytes ]wallet.StringBase64 )
269+ for fieldName , fieldValue := range walletCert .Fields {
270+ fields [wallet .CertificateFieldNameUnder50Bytes (fieldName )] = wallet .StringBase64 (fieldValue )
271+ }
272+
273+ var signature []byte
274+ if walletCert .Signature != nil {
275+ signature = walletCert .Signature .Serialize ()
276+ }
277+
278+ return & Certificate {
279+ Type : certType ,
280+ SerialNumber : serialNumber ,
281+ Subject : subject ,
282+ Certifier : certifier ,
283+ RevocationOutpoint : walletCert .RevocationOutpoint ,
284+ Fields : fields ,
285+ Signature : signature ,
286+ }, nil
287+ }
288+
357289// GetCertificateEncryptionDetails returns protocol ID and key ID for certificate field encryption
358290// For master certificate creation, no serial number is provided because entropy is required
359291// from both the client and the certifier. In this case, the keyID is simply the fieldName.
0 commit comments