Documentation Index Fetch the complete documentation index at: https://mintlify.com/mutuiris/voicepact/llms.txt
Use this file to discover all available pages before exploring further.
Overview
VoicePact uses cryptographic digital signatures to ensure contract authenticity and non-repudiation. Each party’s confirmation is signed with unique cryptographic keys derived from their phone number, creating legally binding digital signatures.
Cryptographic Security Ed25519 elliptic curve signatures for maximum security
Non-Repudiation Parties cannot deny signing once signature is recorded
SMS Integration Simple SMS confirmation creates cryptographic signature
Audit Trail Complete signature history with timestamps and metadata
How It Works
Key Derivation
Unique signing keys are derived from the party’s phone number using PBKDF2 key derivation.
Signature Generation
When a party confirms via SMS/USSD, a digital signature is generated using their derived key.
Signature Recording
The signature is stored in the database with timestamp, IP address, and user agent.
Verification
Signatures can be verified at any time using the original contract data and phone number.
Signature Methods
VoicePact supports multiple signature methods:
sms_confirmation : Reply to SMS with YES/NO command
ussd_confirmation : Confirm through USSD menu
voice_confirmation : Verbal confirmation during call
api_signature : Direct API call with authentication
Creating Signatures
Via SMS Confirmation
When a party replies to a contract SMS:
SMS from +254712345678:
"YES-AG-2024-001234"
The system automatically:
Parses the contract ID
Derives the signing key for the phone number
Generates a cryptographic signature
Records the signature in the database
See crypto_service.py:65 for signature generation.
Via API
Create a signature programmatically:
import httpx
response = httpx.post(
"https://api.voicepact.com/api/v1/signatures/create" ,
json = {
"contract_id" : "AG-2024-001234" ,
"signer_phone" : "+254712345678" ,
"signature_method" : "api_signature" ,
"ip_address" : "41.90.123.45" ,
"user_agent" : "VoicePact-Mobile/1.0"
}
)
result = response.json()
print ( f "Signature ID: { result[ 'signature_id' ] } " )
print ( f "Signature Hash: { result[ 'signature_hash' ] } " )
print ( f "Status: { result[ 'status' ] } " )
Signature Generation
The cryptographic process:
from app.services.crypto_service import get_crypto_service
crypto = get_crypto_service()
# Sign contract data
signature = crypto.sign_contract(
contract_data = " {contract_hash} : {contract_id} : {terms_json} " ,
phone_number = "+254712345678"
)
print ( f "Signature: { signature } " )
# Output: Base64-encoded Ed25519 signature
Signature Components
A signature includes:
Contract Data : Hash of contract content
Phone Number : Signer’s identifier
Timestamp : When signature was created
Message : Combined string that is signed
The message format:
{contract_data}:{phone_number}:{timestamp}
See crypto_service.py:65 for implementation.
Key Derivation
Signing keys are derived from phone numbers:
def _derive_signing_key ( self , phone_number : str ) -> bytes :
"""Derive a unique signing key from phone number"""
kdf = PBKDF2HMAC(
algorithm = hashes.SHA256(),
length = 32 ,
salt = self .salt.encode( 'utf-8' ),
iterations = 100000 ,
)
key_material = f " { self .master_key } : { phone_number } " .encode( 'utf-8' )
return kdf.derive(key_material)
This ensures:
Each phone number has a unique key
Keys are deterministic (same phone = same key)
Keys cannot be reverse-engineered
Master key adds additional security layer
See crypto_service.py:246 for key derivation.
Signature Verification
Verify a signature:
crypto = get_crypto_service()
is_valid = crypto.verify_signature(
contract_data = " {contract_hash} : {contract_id} : {terms_json} " ,
phone_number = "+254712345678" ,
signature = "base64_encoded_signature"
)
if is_valid:
print ( "Signature is valid" )
else :
print ( "Signature verification failed" )
Time-Window Verification
Signatures are verified with time-window tolerance:
# Try multiple time windows to account for clock skew
for time_window in [ 0 , 1 , 2 ]:
test_time = datetime.utcnow().replace(
minute = (datetime.utcnow().minute // 10 - time_window) * 10 ,
second = 0 ,
microsecond = 0
)
test_message = f " { contract_data } : { phone_number } : { test_time.isoformat() } "
try :
public_key.verify(signature_bytes, test_message.encode( 'utf-8' ))
return True
except :
continue
return False
This allows for:
Clock synchronization differences
Network delays
Processing time variations
See crypto_service.py:78 for verification logic.
Database Model
Signatures are stored with complete audit information:
class ContractSignature ( Base ):
__tablename__ = "contract_signatures"
id : Mapped[ int ] = mapped_column(Integer, primary_key = True )
contract_id: Mapped[ str ] = mapped_column(String( 50 ), ForeignKey( "contracts.id" ))
signer_phone: Mapped[ str ] = mapped_column(String( 20 ), index = True )
signature_method: Mapped[ str ] = mapped_column(String( 20 ), default = "sms_confirmation" )
signature_hash: Mapped[ str ] = mapped_column(String( 128 ))
signature_data: Mapped[Optional[ dict ]] = mapped_column( JSON , default = dict )
status: Mapped[SignatureStatus] = mapped_column(
Enum(SignatureStatus),
default = SignatureStatus. PENDING
)
signed_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
created_at: Mapped[datetime] = mapped_column(DateTime, default = datetime.utcnow)
expires_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
ip_address: Mapped[Optional[ str ]] = mapped_column(String( 45 ))
user_agent: Mapped[Optional[ str ]] = mapped_column(String( 200 ))
See contract.py:173 for model definition.
Signature Status
Signatures progress through states:
class SignatureStatus ( str , enum . Enum ):
PENDING = "pending" # Awaiting signature
SIGNED = "signed" # Successfully signed
REJECTED = "rejected" # Party declined
EXPIRED = "expired" # Signature window closed
See contract.py:58 for status enum.
SMS Confirmation Codes
Generate 6-digit confirmation codes:
crypto = get_crypto_service()
code = crypto.generate_sms_confirmation_code(
contract_id = "AG-2024-001234" ,
phone_number = "+254712345678"
)
print ( f "Confirmation code: { code } " ) # e.g., "482051"
Code Verification
is_valid = crypto.verify_sms_confirmation(
contract_id = "AG-2024-001234" ,
phone_number = "+254712345678" ,
code = "482051"
)
if is_valid:
print ( "Code verified - create signature" )
See crypto_service.py:101 for confirmation codes.
Contract Hash Generation
Every contract gets a unique cryptographic hash:
crypto = get_crypto_service()
contract_hash = crypto.generate_contract_hash(
content = f " { transcript } : { terms_json } : { parties_list } : { timestamp } "
)
print ( f "Contract Hash: { contract_hash } " )
# Output: 64-character hexadecimal hash
Hash Algorithm
VoicePact uses BLAKE2b or SHA-256:
if settings.contract_hash_algorithm == "blake2b" :
hash_obj = hashlib.blake2b(content_bytes, digest_size = 32 )
else :
hash_obj = hashlib.sha256(content_bytes)
return hash_obj.hexdigest()
See crypto_service.py:51 for hash generation.
Contract Integrity Validation
Verify contract hasn’t been tampered with:
crypto = get_crypto_service()
original_hash = contract.contract_hash
current_content = f " { contract.transcript } : { contract.terms } : { contract.parties } "
is_valid = crypto.validate_contract_integrity(
original_hash = original_hash,
current_content = current_content
)
if not is_valid:
raise Exception ( "Contract has been tampered with!" )
See crypto_service.py:238 for integrity validation.
Audit Signatures
Every contract action is signed for audit trail:
crypto = get_crypto_service()
audit_sig = crypto.create_audit_signature(
action = "contract_signed" ,
contract_id = "AG-2024-001234" ,
actor = "+254712345678" ,
data = {
"signature_method" : "sms_confirmation" ,
"ip_address" : "41.90.123.45" ,
"timestamp" : "2024-03-06T10:30:00Z"
}
)
print ( f "Audit Signature: { audit_sig } " )
# Format: {timestamp}:{signature_hash}
Audit Verification
is_valid = crypto.verify_audit_signature(
signature = audit_sig,
action = "contract_signed" ,
contract_id = "AG-2024-001234" ,
actor = "+254712345678" ,
data = original_data
)
See crypto_service.py:130 for audit signatures.
Key Pair Generation
Generate Ed25519 key pairs:
crypto = get_crypto_service()
private_key, public_key = crypto.generate_key_pair()
print ( f "Private Key: { private_key[: 50 ] } ..." )
print ( f "Public Key: { public_key[: 50 ] } ..." )
Keys are Base64-encoded PEM format:
private_key = ed25519.Ed25519PrivateKey.generate()
public_key = private_key.public_key()
private_pem = private_key.private_bytes(
encoding = serialization.Encoding. PEM ,
format = serialization.PrivateFormat. PKCS8 ,
encryption_algorithm = serialization.NoEncryption()
)
public_pem = public_key.public_bytes(
encoding = serialization.Encoding. PEM ,
format = serialization.PublicFormat.SubjectPublicKeyInfo
)
return (
base64.b64encode(private_pem).decode( 'utf-8' ),
base64.b64encode(public_pem).decode( 'utf-8' )
)
See crypto_service.py:27 for key generation.
Webhook Signatures
Secure webhook payloads with HMAC signatures:
crypto = get_crypto_service()
# Generate signature for outgoing webhook
payload = json.dumps(webhook_data)
signature = crypto.generate_webhook_signature(payload)
headers = {
"X-VoicePact-Signature" : signature,
"Content-Type" : "application/json"
}
Webhook Verification
# Verify incoming webhook
payload = await request.body()
signature = request.headers.get( "X-Webhook-Signature" )
is_valid = crypto.verify_webhook_signature(
payload = payload.decode(),
signature = signature
)
if not is_valid:
raise HTTPException( status_code = 401 , detail = "Invalid signature" )
See crypto_service.py:162 for webhook signatures.
Session Tokens
Generate secure session tokens for USSD/API:
crypto = get_crypto_service()
token = crypto.generate_session_token(
phone_number = "+254712345678" ,
session_type = "ussd"
)
print ( f "Session Token: { token } " )
# Output: URL-safe Base64 string
See crypto_service.py:227 for session tokens.
Data Encryption
Encrypt sensitive contract data:
crypto = get_crypto_service()
# Encrypt
encrypted = crypto.encrypt_sensitive_data(
data = "Sensitive payment details" ,
context = "payment_info"
)
# Decrypt
decrypted = crypto.decrypt_sensitive_data(
encrypted_data = encrypted,
context = "payment_info"
)
Encryption uses:
PBKDF2 key derivation
100,000 iterations
Random salt per encryption
Context-specific keys
See crypto_service.py:184 for encryption.
Payment References
Generate unique payment reference codes:
crypto = get_crypto_service()
reference = crypto.generate_payment_reference(
contract_id = "AG-2024-001234" ,
amount = 350000.00 ,
phone_number = "+254712345678"
)
print ( f "Payment Reference: { reference } " )
# Output: 16-character uppercase hex (e.g., "A7F5C3D8E9B2F1D0")
See crypto_service.py:121 for payment references.
Contract Verification Codes
Generate human-readable verification codes:
crypto = get_crypto_service()
code = crypto.generate_contract_verification_code(
contract_id = "AG-2024-001234"
)
print ( f "Verification Code: { code } " )
# Output: "VC-A7F5C3D8"
See crypto_service.py:265 for verification codes.
Security Best Practices
Store master keys in secure environment variables
Use different keys for development/production
Rotate keys periodically
Never log private keys
Always verify signatures before trusting data
Check signature expiration times
Validate signer authority for contract
Log failed verification attempts
Sign all critical actions
Store complete signature metadata
Include IP addresses and timestamps
Make audit logs immutable
Verify all webhook signatures
Use HTTPS for webhook endpoints
Implement replay attack prevention
Rate limit webhook processing
Error Handling
from app.services.crypto_service import CryptographicError
try :
signature = crypto.sign_contract(contract_data, phone_number)
except CryptographicError as e:
logger.error( f "Signature generation failed: { e } " )
# Handle error - notify user, retry, etc.
try :
is_valid = crypto.verify_signature(contract_data, phone_number, signature)
if not is_valid:
raise HTTPException( status_code = 401 , detail = "Invalid signature" )
except CryptographicError as e:
logger.error( f "Signature verification failed: { e } " )
# Treat as invalid signature
raise HTTPException( status_code = 401 , detail = "Signature verification error" )
Legal Considerations
Digital signatures in VoicePact:
Legally Binding : Meet requirements for electronic signatures in many jurisdictions
Non-Repudiation : Parties cannot deny signing
Authentication : Phone number confirms identity
Integrity : Any tampering is detectable
Audit Trail : Complete record of all signatures
Consult local regulations for specific requirements in your jurisdiction.
Signature operations are highly optimized:
Key Derivation : ~100ms (cached per session)
Signature Generation : <10ms
Signature Verification : <10ms
Hash Generation : <1ms
Next Steps
Voice Contracts Create contracts that require signatures
SMS Verification Understand SMS-based signing
USSD Integration Sign via USSD menus
Mobile Money Cryptographically secure payments