@@ -11,7 +11,7 @@ import { ProtocolAuthorization } from '../../../core/protocol-authorization.js';
1111import { removeUndefinedProperties } from '../../../utils/object.js' ;
1212
1313import { authorize , validateAuthorizationIntegrity } from '../../../core/auth.js' ;
14- import { computeCid , getDagPbCid } from '../../../utils/cid.js' ;
14+ import { computeCid , getDagPbCid , parseCid } from '../../../utils/cid.js' ;
1515import { DwnInterfaceName , DwnMethodName } from '../../../core/message.js' ;
1616import { GeneralJws , SignatureInput } from '../../../jose/jws/general/types.js' ;
1717
@@ -47,15 +47,20 @@ export class RecordsWrite extends Message {
4747 * RecordsWrite message adhering to the DWN specification.
4848 */
4949 readonly message : RecordsWriteMessage ;
50+ readonly attesters : string [ ] ;
5051
5152 private constructor ( message : RecordsWriteMessage ) {
5253 super ( message ) ;
5354
55+ this . attesters = RecordsWrite . getAttesters ( message ) ;
56+
5457 // consider converting isInitialWrite() & getEntryId() into properties for performance and convenience
5558 }
5659
5760 public static async parse ( message : RecordsWriteMessage ) : Promise < RecordsWrite > {
61+ // asynchronous checks that are required by the constructor to initialize members properly
5862 await validateAuthorizationIntegrity ( message , { allowedProperties : new Set ( [ 'recordId' , 'contextId' , 'attestationCid' ] ) } ) ;
63+ await RecordsWrite . validateAttestationIntegrity ( message ) ;
5964
6065 const recordsWrite = new RecordsWrite ( message ) ;
6166
@@ -278,6 +283,37 @@ export class RecordsWrite extends Message {
278283 }
279284 }
280285
286+ /**
287+ * Validates the structural integrity of the `attestation` property.
288+ * NOTE: signature is not verified.
289+ */
290+ private static async validateAttestationIntegrity ( message : RecordsWriteMessage ) : Promise < void > {
291+ if ( message . attestation === undefined ) {
292+ return ;
293+ }
294+
295+ // TODO: multi-attesters to be unblocked by #205 - Revisit database interfaces (https://github.com/TBD54566975/dwn-sdk-js/issues/205)
296+ if ( message . attestation . signatures . length !== 1 ) {
297+ throw new Error ( `Currently implementation only supports 1 attester, but got ${ message . attestation . signatures . length } ` ) ;
298+ }
299+
300+ const payloadJson = GeneralJwsVerifier . decodePlainObjectPayload ( message . attestation ) ;
301+ const { descriptorCid } = payloadJson ;
302+
303+ // `descriptorCid` validation - ensure that the provided descriptorCid matches the CID of the actual message
304+ const providedDescriptorCid = parseCid ( descriptorCid ) ; // parseCid throws an exception if parsing fails
305+ const expectedDescriptorCid = await computeCid ( message . descriptor ) ;
306+ if ( ! providedDescriptorCid . equals ( expectedDescriptorCid ) ) {
307+ throw new Error ( `descriptorCid ${ providedDescriptorCid } does not match expected descriptorCid ${ expectedDescriptorCid } ` ) ;
308+ }
309+
310+ // check to ensure that no other unexpected properties exist in payload.
311+ const propertyCount = Object . keys ( payloadJson ) . length ;
312+ if ( propertyCount > 1 ) {
313+ throw new Error ( `Only 'descriptorCid' is allowed in attestation payload, but got ${ propertyCount } properties.` ) ;
314+ }
315+ } ;
316+
281317 /**
282318 * Computes the deterministic Entry ID of this message.
283319 */
@@ -403,4 +439,13 @@ export class RecordsWrite extends Message {
403439
404440 return true ;
405441 }
442+
443+ /**
444+ * Gets the DID of the author of the given message.
445+ */
446+ public static getAttesters ( message : RecordsWriteMessage ) : string [ ] {
447+ const attestationSignatures = message . attestation ?. signatures ?? [ ] ;
448+ const attesters = attestationSignatures . map ( ( signature ) => GeneralJwsVerifier . getDid ( signature ) ) ;
449+ return attesters ;
450+ }
406451}
0 commit comments