@@ -26,6 +26,11 @@ const (
2626
2727 // ProofTypeDataIntegrity is the W3C Data Integrity proof type
2828 ProofTypeDataIntegrity = "DataIntegrityProof"
29+
30+ // JSON-LD keys
31+ keyContext = "@context"
32+ keyProof = "proof"
33+ keyProofValue = "proofValue"
2934)
3035
3136// Suite implements the eddsa-jcs-2022 cryptosuite for W3C Data Integrity proofs.
@@ -59,34 +64,49 @@ type DataIntegrityProof struct {
5964
6065// Sign creates a Data Integrity proof for a JSON document using eddsa-jcs-2022.
6166func (s * Suite ) Sign (document any , key ed25519.PrivateKey , opts * SignOptions ) (map [string ]any , error ) {
67+ if err := validateSignInputs (document , key , opts ); err != nil {
68+ return nil , err
69+ }
70+
71+ docMap , err := toMap (document )
72+ if err != nil {
73+ return nil , fmt .Errorf ("failed to convert document to map: %w" , err )
74+ }
75+
76+ docWithoutProof := copyMapWithoutKey (docMap , keyProof )
77+ proofConfig := buildProofConfig (opts , docWithoutProof )
78+
79+ proofValue , err := createSignature (docWithoutProof , proofConfig , key )
80+ if err != nil {
81+ return nil , err
82+ }
83+ proofConfig [keyProofValue ] = proofValue
84+
85+ return buildSignedDocument (docMap , proofConfig ), nil
86+ }
87+
88+ // validateSignInputs validates the inputs for the Sign method.
89+ func validateSignInputs (document any , key ed25519.PrivateKey , opts * SignOptions ) error {
6290 if document == nil {
63- return nil , fmt .Errorf ("document is nil" )
91+ return fmt .Errorf ("document is nil" )
6492 }
6593 if key == nil {
66- return nil , fmt .Errorf ("private key is nil" )
94+ return fmt .Errorf ("private key is nil" )
6795 }
6896 if opts == nil {
69- return nil , fmt .Errorf ("sign options are nil" )
97+ return fmt .Errorf ("sign options are nil" )
7098 }
7199 if opts .VerificationMethod == "" {
72- return nil , fmt .Errorf ("verificationMethod is required" )
100+ return fmt .Errorf ("verificationMethod is required" )
73101 }
74102 if opts .ProofPurpose == "" {
75- return nil , fmt .Errorf ("proofPurpose is required" )
76- }
77-
78- docMap , err := toMap (document )
79- if err != nil {
80- return nil , fmt .Errorf ("failed to convert document to map: %w" , err )
81- }
82-
83- docWithoutProof := make (map [string ]any )
84- for k , v := range docMap {
85- if k != "proof" {
86- docWithoutProof [k ] = v
87- }
103+ return fmt .Errorf ("proofPurpose is required" )
88104 }
105+ return nil
106+ }
89107
108+ // buildProofConfig creates the proof configuration object.
109+ func buildProofConfig (opts * SignOptions , docWithoutProof map [string ]any ) map [string ]any {
90110 created := opts .Created
91111 if created .IsZero () {
92112 created = time .Now ().UTC ()
@@ -109,18 +129,23 @@ func (s *Suite) Sign(document any, key ed25519.PrivateKey, opts *SignOptions) (m
109129
110130 // Per W3C eddsa-jcs-2022 spec Section 3.3.1 step 2:
111131 // If unsecuredDocument.@context is present, set proof.@context to unsecuredDocument.@context
112- if ctx , ok := docWithoutProof ["@context" ]; ok {
113- proofConfig ["@context" ] = ctx
132+ if ctx , ok := docWithoutProof [keyContext ]; ok {
133+ proofConfig [keyContext ] = ctx
114134 }
115135
136+ return proofConfig
137+ }
138+
139+ // createSignature generates the signature for the document and proof config.
140+ func createSignature (docWithoutProof , proofConfig map [string ]any , key ed25519.PrivateKey ) (string , error ) {
116141 docCanonical , err := Canonicalize (docWithoutProof )
117142 if err != nil {
118- return nil , fmt .Errorf ("failed to canonicalize document: %w" , err )
143+ return "" , fmt .Errorf ("failed to canonicalize document: %w" , err )
119144 }
120145
121146 proofCanonical , err := Canonicalize (proofConfig )
122147 if err != nil {
123- return nil , fmt .Errorf ("failed to canonicalize proof config: %w" , err )
148+ return "" , fmt .Errorf ("failed to canonicalize proof config: %w" , err )
124149 }
125150
126151 docHash := sha256 .Sum256 (docCanonical )
@@ -131,32 +156,43 @@ func (s *Suite) Sign(document any, key ed25519.PrivateKey, opts *SignOptions) (m
131156
132157 proofValue , err := multibase .Encode (multibase .Base58BTC , signature )
133158 if err != nil {
134- return nil , fmt .Errorf ("failed to encode signature: %w" , err )
159+ return "" , fmt .Errorf ("failed to encode signature: %w" , err )
135160 }
136161
137- proofConfig ["proofValue" ] = proofValue
162+ return proofValue , nil
163+ }
138164
139- result := make (map [string ]any )
140- for k , v := range docMap {
141- if k != "proof" {
142- result [k ] = v
143- }
165+ // buildSignedDocument creates the result document with the proof attached.
166+ func buildSignedDocument (docMap , proofConfig map [string ]any ) map [string ]any {
167+ result := copyMapWithoutKey (docMap , keyProof )
168+
169+ existingProof , ok := docMap [keyProof ]
170+ if ! ok {
171+ result [keyProof ] = proofConfig
172+ return result
144173 }
145174
146- if existingProof , ok := docMap ["proof" ]; ok {
147- switch p := existingProof .(type ) {
148- case []any :
149- result ["proof" ] = append (p , proofConfig )
150- case map [string ]any :
151- result ["proof" ] = []any {p , proofConfig }
152- default :
153- result ["proof" ] = proofConfig
154- }
155- } else {
156- result ["proof" ] = proofConfig
175+ switch p := existingProof .(type ) {
176+ case []any :
177+ result [keyProof ] = append (p , proofConfig )
178+ case map [string ]any :
179+ result [keyProof ] = []any {p , proofConfig }
180+ default :
181+ result [keyProof ] = proofConfig
157182 }
158183
159- return result , nil
184+ return result
185+ }
186+
187+ // copyMapWithoutKey creates a shallow copy of a map excluding a specific key.
188+ func copyMapWithoutKey (src map [string ]any , excludeKey string ) map [string ]any {
189+ result := make (map [string ]any )
190+ for k , v := range src {
191+ if k != excludeKey {
192+ result [k ] = v
193+ }
194+ }
195+ return result
160196}
161197
162198// Verify verifies an eddsa-jcs-2022 Data Integrity proof on a document.
@@ -180,60 +216,71 @@ func (s *Suite) VerifyWithProof(document map[string]any, proof map[string]any, p
180216 return fmt .Errorf ("public key is nil" )
181217 }
182218
219+ signatureBytes , err := validateAndDecodeProof (proof )
220+ if err != nil {
221+ return err
222+ }
223+
224+ docWithoutProof := copyMapWithoutKey (document , keyProof )
225+ proofConfig := copyMapWithoutKey (proof , keyProofValue )
226+
227+ if err := validateContext (docWithoutProof , proofConfig ); err != nil {
228+ return err
229+ }
230+
231+ return verifySignature (docWithoutProof , proofConfig , signatureBytes , publicKey )
232+ }
233+
234+ // validateAndDecodeProof validates the proof structure and decodes the signature.
235+ func validateAndDecodeProof (proof map [string ]any ) ([]byte , error ) {
183236 proofType , _ := getString (proof , "type" )
184237 if proofType != ProofTypeDataIntegrity {
185- return fmt .Errorf ("invalid proof type: expected %s, got %s" , ProofTypeDataIntegrity , proofType )
238+ return nil , fmt .Errorf ("invalid proof type: expected %s, got %s" , ProofTypeDataIntegrity , proofType )
186239 }
187240
188241 cryptosuite , _ := getString (proof , "cryptosuite" )
189242 if cryptosuite != CryptosuiteEdDSAJCS2022 {
190- return fmt .Errorf ("invalid cryptosuite: expected %s, got %s" , CryptosuiteEdDSAJCS2022 , cryptosuite )
243+ return nil , fmt .Errorf ("invalid cryptosuite: expected %s, got %s" , CryptosuiteEdDSAJCS2022 , cryptosuite )
191244 }
192245
193- proofValue , ok := getString (proof , "proofValue" )
246+ proofValue , ok := getString (proof , keyProofValue )
194247 if ! ok || proofValue == "" {
195- return fmt .Errorf ("proof is missing proofValue" )
248+ return nil , fmt .Errorf ("proof is missing proofValue" )
196249 }
197250
198251 _ , signatureBytes , err := multibase .Decode (proofValue )
199252 if err != nil {
200- return fmt .Errorf ("failed to decode proofValue: %w" , err )
253+ return nil , fmt .Errorf ("failed to decode proofValue: %w" , err )
201254 }
202255
203256 if len (signatureBytes ) != ed25519 .SignatureSize {
204- return fmt .Errorf ("invalid signature length: expected %d, got %d" , ed25519 .SignatureSize , len (signatureBytes ))
257+ return nil , fmt .Errorf ("invalid signature length: expected %d, got %d" , ed25519 .SignatureSize , len (signatureBytes ))
205258 }
206259
207- docWithoutProof := make (map [string ]any )
208- for k , v := range document {
209- if k != "proof" {
210- docWithoutProof [k ] = v
211- }
212- }
260+ return signatureBytes , nil
261+ }
213262
214- proofConfig := make ( map [ string ] any )
215- for k , v := range proof {
216- if k != "proofValue" {
217- proofConfig [ k ] = v
218- }
263+ // validateContext validates @context per W3C eddsa-jcs-2022 spec Section 3.3.2 steps 4.1-4.2.
264+ func validateContext ( docWithoutProof , proofConfig map [ string ] any ) error {
265+ proofCtx , ok := proofConfig [ keyContext ]
266+ if ! ok {
267+ return nil
219268 }
220269
221- // Per W3C eddsa-jcs-2022 spec Section 3.3.2 steps 4.1-4.2:
222- // If proofOptions.@context exists:
223- // - Check that document.@context starts with all values in proofOptions.@context
224- // - Set unsecuredDocument.@context equal to proofOptions.@context
225- if proofCtx , ok := proofConfig ["@context" ]; ok {
226- docCtx , hasDocCtx := docWithoutProof ["@context" ]
227- if ! hasDocCtx {
228- return fmt .Errorf ("proof has @context but document does not" )
229- }
230- if ! contextStartsWith (docCtx , proofCtx ) {
231- return fmt .Errorf ("document @context does not start with proof @context values" )
232- }
233- // Set unsecuredDocument.@context equal to proofOptions.@context
234- docWithoutProof ["@context" ] = proofCtx
270+ docCtx , hasDocCtx := docWithoutProof [keyContext ]
271+ if ! hasDocCtx {
272+ return fmt .Errorf ("proof has @context but document does not" )
273+ }
274+ if ! contextStartsWith (docCtx , proofCtx ) {
275+ return fmt .Errorf ("document @context does not start with proof @context values" )
235276 }
277+ // Set unsecuredDocument.@context equal to proofOptions.@context
278+ docWithoutProof [keyContext ] = proofCtx
279+ return nil
280+ }
236281
282+ // verifySignature verifies the Ed25519 signature.
283+ func verifySignature (docWithoutProof , proofConfig map [string ]any , signatureBytes []byte , publicKey ed25519.PublicKey ) error {
237284 docCanonical , err := Canonicalize (docWithoutProof )
238285 if err != nil {
239286 return fmt .Errorf ("failed to canonicalize document: %w" , err )
@@ -291,7 +338,7 @@ func toMap(data any) (map[string]any, error) {
291338
292339// findJCSProof finds the first eddsa-jcs-2022 proof in a document.
293340func findJCSProof (doc map [string ]any ) (map [string ]any , error ) {
294- proofVal , ok := doc ["proof" ]
341+ proofVal , ok := doc [keyProof ]
295342 if ! ok {
296343 return nil , fmt .Errorf ("document has no proof" )
297344 }
0 commit comments