Self provides a secure digital agreement signing workflow that allows users to cryptographically sign documents using their digital identity. This workflow ensures document integrity, non-repudiation, and tamper-proof signatures that can be verified independently. The digital signing process is designed to be scalable and can be used in a variety of scenarios, such as:
Legal contract signing for business applications
Terms of service acceptance for web and mobile platforms
Document approval workflows in enterprise systems
Regulatory compliance documentation
Financial agreement signing
Healthcare consent forms
How to build a digital agreement signing workflow¶
You can build a secure digital agreement signing workflow with Self using verification requests and responses. Let's break down the process into steps:
Once we have the user's address (userAddress) and the document to be signed, we can send a verification request. This request contains the document details and signing requirements. For more details on verification requests, check out the Verification guide.
// Create the document to be signeddocumentHash:="sha256:a1b2c3d4e5f6..."// SHA-256 hash of the documentdocumentContent:="Terms of Service Agreement v2.1..."content,err:=message.NewVerificationRequest().Type([]string{"VerifiablePresentation","DocumentSigning"}).Object(documentHash).Subject("document_signature").Description("Please review and sign this Terms of Service Agreement").Attachment([]byte(documentContent)).Finish()iferr!=nil{log.Fatal("failed to encode verification request message","error",err)}err=selfAccount.MessageSend(userAddress,content)iferr!=nil{log.Fatal("failed to send verification request message","error",err)}
valdocumentHash="sha256:a1b2c3d4e5f6..."// SHA-256 hash of the documentvaldocumentContent="Terms of Service Agreement v2.1..."valcontent=NewVerificationRequest().presentationType(arrayOf("VerifiablePresentation","DocumentSigning")).obj(documentHash).subject("document_signature").description("Please review and sign this Terms of Service Agreement").attachment(documentContent.toByteArray()).finish()valsendStatus=account.messageSend(userAddress,content)println("send VerificationRequest status: ${sendStatus.code()} - requestId:${verificationRequest.id().toHexString()}")
You need to listen to the verification request in the account's callback and then check the incoming message for its type and details. If the request is for document signing, display the document content to the user for review and signature.
varverificationRequest:VerificationRequest? =nullaccount.setOnRequestListener{msg->when(msg){isVerificationRequest->{if(msg.types().contains("DocumentSigning")&&msg.subject()=="document_signature"){verificationRequest=msg// Extract document content from the requestvaldocumentContent=String(msg.attachment())valdocumentHash=msg.obj()// Navigate to document signing UInavController.navigate("documentSigningRoute")}}}}// Integrate document signing UI flow into NavigationaddDocumentSigningRoute(navController,route="documentSigningRoute",selfModifier=selfModifier,document={documentContent},onFinish={accepted->if(verificationRequest!=null){// Send the verification response after user decisionsendVerificationResponse(accepted)}})funsendVerificationResponse(accepted:Boolean){valstatus=if(accepted)ResponseStatus.acceptedelseResponseStatus.rejectedvalverificationResponse=VerificationResponse.Builder().setRequestId(verificationRequest.id()).setTypes(verificationRequest.types()).setToIdentifier(verificationRequest.toIdentifier()).setFromIdentifier(verificationRequest.fromIdentifier()).setStatus(status).setObject(verificationRequest.obj())// Include document hash.setSubject(verificationRequest.subject()).build()account.send(verificationResponse)}
// 1. Handle verification request in your request listeneraccount.setOnRequestListener{[weakself]requestMessageinguardletself=selfelse{return}ifletverificationRequest=requestMessageas?VerificationRequest{print("Verification request received from: \(verificationRequest.fromIdentifier())")Task{@MainActorinawaitself.handleVerificationRequest(verificationRequest)}}}// 2. Handle verification request with proper filtering and thread safetyprivatefunchandleVerificationRequest(_verificationRequest:VerificationRequest)async{lettypes=verificationRequest.types()letsubject=verificationRequest.subject()print("Verification request types: \(types)")print("Verification request subject: \(subject)")// Check if this is a document signing requestiftypes.contains("DocumentSigning")&&subject=="document_signature"{print("✅ Processing document signing request")awaithandleDocumentSigning(verificationRequest)}else{print("❌ Unsupported verification request type")}}// 3. Handle document signing with proper thread managementprivatefunchandleDocumentSigning(_verificationRequest:VerificationRequest)async{// Extract document detailsletdocumentHash=verificationRequest.obj()letdocumentDescription=verificationRequest.description()// Extract document content if availablevardocumentContent:String=""ifletattachment=verificationRequest.attachment(){documentContent=String(data:attachment,encoding:.utf8)??""}print("Document to sign:")print("Hash: \(documentHash)")print("Description: \(documentDescription)")print("Content preview: \(String(documentContent.prefix(100)))...")// Store the request for later responsecurrentVerificationRequest=verificationRequest// Navigate to document signing UI on main threadawaitMainActor.run{// Update UI to show document signing screencurrentScreen=.documentSigning}}// 4. Send verification response after user makes signing decisionprivatefuncsendVerificationResponse(torequest:VerificationRequest,accepted:Bool)async{letstatus=accepted?ResponseStatus.accepted:ResponseStatus.rejecteddo{letresponse=VerificationResponse.Builder().withRequestId(request.id()).withTypes(request.types()).toIdentifier(request.toIdentifier()).fromIdentifier(request.fromIdentifier()).withStatus(status).withObject(request.obj())// Include document hash.withSubject(request.subject()).build()tryawaitaccount.send(message:response,onAcknowledgement:{messageId,errorinifleterror=error{print("❌ Verification response failed: \(error)")}else{print("✅ Document signing response sent successfully")}})}catch{print("Error building verification response: \(error)")}}// 5. User interface methods for accepting/rejecting signatureprivatefuncacceptDocumentSigning(){guardletrequest=currentVerificationRequestelse{return}Task{awaitsendVerificationResponse(to:request,accepted:true)awaitMainActor.run{currentScreen=.signatureResult(success:true)}}}privatefuncrejectDocumentSigning(){guardletrequest=currentVerificationRequestelse{return}Task{awaitsendVerificationResponse(to:request,accepted:false)awaitMainActor.run{currentScreen=.signatureResult(success:false)}}}
After receiving the verification response, we need to process the signature. This involves validating the response, checking the signature status, and storing the signed document record. For more information on verification validation, see the Verification Validation guide.
// Process the verification responseifresponse.Status()==message.ResponseStatusAccepted{log.Printf("INFO: Document signed successfully by %s",response.FromIdentifier())// Validate the response signatureerr=response.Validate()iferr!=nil{log.Printf("WARN: failed to validate verification response - error: %v",err)return}// Extract signature detailsdocumentHash:=response.Object()subject:=response.Subject()signerID:=response.FromIdentifier()signedAt:=response.CreatedAt()// Store the signature recordsignatureRecord:=SignatureRecord{DocumentHash:documentHash,SignerID:signerID,SignedAt:signedAt,Status:"signed",RequestID:response.RequestID(),}err=storeSignatureRecord(signatureRecord)iferr!=nil{log.Printf("ERROR: failed to store signature record - error: %v",err)}else{log.Printf("INFO: Signature record stored successfully for document %s",documentHash)}}elseifresponse.Status()==message.ResponseStatusRejected{log.Printf("INFO: Document signing rejected by %s",response.FromIdentifier())// Store rejection recordrejectionRecord:=SignatureRecord{DocumentHash:response.Object(),SignerID:response.FromIdentifier(),SignedAt:response.CreatedAt(),Status:"rejected",RequestID:response.RequestID(),}err=storeSignatureRecord(rejectionRecord)iferr!=nil{log.Printf("ERROR: failed to store rejection record - error: %v",err)}}
// Process the verification responsewhen(response.status()){ResponseStatus.accepted->{println("Document signed successfully by ${response.fromIdentifier()}")// Validate the response signatureresponse.validate()// Extract signature detailsvaldocumentHash=response.obj()valsubject=response.subject()valsignerID=response.fromIdentifier()valsignedAt=response.createdAt()// Store the signature recordvalsignatureRecord=SignatureRecord(documentHash=documentHash,signerID=signerID,signedAt=signedAt,status="signed",requestID=response.requestId())storeSignatureRecord(signatureRecord)println("Signature record stored successfully for document $documentHash")}ResponseStatus.rejected->{println("Document signing rejected by ${response.fromIdentifier()}")// Store rejection recordvalrejectionRecord=SignatureRecord(documentHash=response.obj(),signerID=response.fromIdentifier(),signedAt=response.createdAt(),status="rejected",requestID=response.requestId())storeSignatureRecord(rejectionRecord)}}
typeSignatureRecordstruct{DocumentHashstring`json:"document_hash"`SignerIDstring`json:"signer_id"`SignedAttime.Time`json:"signed_at"`Statusstring`json:"status"`// "signed", "rejected", "pending"RequestIDstring`json:"request_id"`IPAddressstring`json:"ip_address,omitempty"`UserAgentstring`json:"user_agent,omitempty"`Geolocationstring`json:"geolocation,omitempty"`}funcVerifyDocumentSignature(documentHash,signerIDstring)(*SignatureRecord,error){// Retrieve signature record from databaserecord,err:=getSignatureRecord(documentHash,signerID)iferr!=nil{returnnil,fmt.Errorf("signature record not found: %w",err)}// Verify the signature is still validifrecord.Status!="signed"{returnnil,fmt.Errorf("document not signed or signature rejected")}// Additional integrity checksiftime.Since(record.SignedAt)>time.Hour*24*365{// 1 year expirylog.Printf("WARN: signature is older than 1 year")}returnrecord,nil}
dataclassSignatureRecord(valdocumentHash:String,valsignerID:String,valsignedAt:Date,valstatus:String,// "signed", "rejected", "pending"valrequestID:String,valipAddress:String?=null,valuserAgent:String?=null,valgeolocation:String?=null)funverifyDocumentSignature(documentHash:String,signerID:String):SignatureRecord? {// Retrieve signature record from databasevalrecord=getSignatureRecord(documentHash,signerID)?:throwIllegalArgumentException("Signature record not found")// Verify the signature is still validif(record.status!="signed"){throwIllegalStateException("Document not signed or signature rejected")}// Additional integrity checksvaldaysSinceSigning=(Date().time-record.signedAt.time)/(1000*60*60*24)if(daysSinceSigning>365){// 1 year expiryprintln("WARN: signature is older than 1 year")}returnrecord}
This example demonstrates how to build a comprehensive digital agreement signing workflow with Self. We've seen how to send verification requests for document signing, handle the signing process on mobile clients, validate signature responses, and maintain audit trails for compliance.
The Self SDK provides a secure, legally-compliant foundation for digital document signing that ensures: - Identity verification: Signers are authenticated using their Self digital identity - Document integrity: Cryptographic hashes prevent document tampering - Non-repudiation: Signatures cannot be denied or forged - Audit compliance: Comprehensive logging for regulatory requirements
Key Features Demonstrated: - Document Hashing: Cryptographic document integrity verification - Signature Workflows: Complete request/response patterns for document signing - Audit Trails: Comprehensive logging and signature record management - Legal Compliance: Best practices for electronic signature validity
By leveraging these Self features, you can create a robust, secure, and legally-compliant digital signing system that streamlines agreement processes while maintaining the highest security standards.