use serde::{Deserialize, Serialize};
use crate::{Address, BlockHeight, Timestamp};
pub type AgreementId = [u8; 32];
pub type SignatureId = [u8; 32];
pub type AttestationId = [u8; 32];
pub type IpAssetId = [u8; 32];
pub type ExecutorLinkId = [u8; 32];
pub type PolicyId = [u8; 32];
pub type ProofId = [u8; 32];
pub type SubjectId = [u8; 32];
pub const AGREEMENT_DOMAIN_SEP: &[u8] = b"SRC841-AGREEMENT:";
pub const AGREEMENT_COMMITMENT_SEP: &[u8] = b"SRC841-COMMITMENT:v1:";
pub const PARTY_COMMITMENT_SEP: &[u8] = b"SRC841-PARTY:v1:";
pub const SIGNATURE_DOMAIN_SEP: &[u8] = b"SRC842-SIGNATURE:";
pub const SIGNATURE_MESSAGE_SEP: &[u8] = b"SRC842-SIGN-MSG:v1:";
pub const ATTESTATION_DOMAIN_SEP: &[u8] = b"SRC843-ATTESTATION:";
pub const NOTARY_COMMITMENT_SEP: &[u8] = b"SRC843-NOTARY:v1:";
pub const IP_ACTION_DOMAIN_SEP: &[u8] = b"SRC844-IP-ACTION:";
pub const IP_SCOPE_COMMITMENT_SEP: &[u8] = b"SRC844-SCOPE:v1:";
pub const EXECUTOR_LINK_DOMAIN_SEP: &[u8] = b"SRC845-EXECUTOR:";
pub const TERMS_COMMITMENT_SEP: &[u8] = b"SRC845-TERMS:v1:";
pub const PROOF_PROFILE_DOMAIN_SEP: &[u8] = b"SRC846-PROOF:";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PartyRef {
Commitment([u8; 32]),
Subject(SubjectId),
}
impl PartyRef {
pub fn generate_commitment(subject: &SubjectId, salt: &[u8; 32]) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(PARTY_COMMITMENT_SEP);
hasher.update(subject);
hasher.update(salt);
*hasher.finalize().as_bytes()
}
pub fn as_hash(&self) -> [u8; 32] {
match self {
PartyRef::Commitment(c) => *c,
PartyRef::Subject(s) => *s,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum AgreementRole {
Buyer = 0,
Seller = 1,
Employer = 2,
Employee = 3,
Landlord = 4,
Tenant = 5,
Licensor = 6,
Licensee = 7,
Lender = 8,
Borrower = 9,
Guarantor = 10,
Beneficiary = 11,
Trustee = 12,
Settlor = 13,
Assignor = 14,
Assignee = 15,
Principal = 16,
Agent = 17,
Partner = 18,
Shareholder = 19,
Witness = 20,
Notary = 21,
Mediator = 22,
Arbitrator = 23,
Other = 255,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AttachmentRef {
pub payload_hash: [u8; 32],
pub payload_size: u64,
pub hint_uri: Option<String>,
pub encryption_meta: Option<EncryptionMeta>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EncryptionMeta {
pub algorithm: EncryptionAlgorithm,
pub key_commitment: Option<[u8; 32]>,
pub nonce: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum EncryptionAlgorithm {
Aes256Gcm = 0,
ChaCha20Poly1305 = 1,
X25519Aes256Gcm = 2,
ThresholdEncryption = 3,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PartyBinding {
pub party_ref: PartyRef,
pub role: AgreementRole,
pub signed: bool,
pub signed_at: Option<Timestamp>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum AgreementStatus {
Draft = 0,
PendingSignatures = 1,
Executed = 2,
Active = 3,
Expired = 4,
Terminated = 5,
Superseded = 6,
Voided = 7,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AgreementCommitment {
pub agreement_id: AgreementId,
pub agreement_commitment: [u8; 32],
pub parties: Vec<PartyBinding>,
pub jurisdiction_code: String,
pub effective_from: Option<Timestamp>,
pub expiry: Option<Timestamp>,
pub attachments: Vec<AttachmentRef>,
pub policy_id: PolicyId,
pub status: AgreementStatus,
pub created_at: Timestamp,
pub updated_at: Timestamp,
pub created_at_height: BlockHeight,
pub supersedes: Option<AgreementId>,
}
impl AgreementCommitment {
pub fn generate_id(
creator: &Address,
agreement_commitment: &[u8; 32],
nonce: &[u8; 32],
) -> AgreementId {
let mut hasher = blake3::Hasher::new();
hasher.update(AGREEMENT_DOMAIN_SEP);
hasher.update(b":v1:");
hasher.update(creator.as_ref());
hasher.update(agreement_commitment);
hasher.update(nonce);
*hasher.finalize().as_bytes()
}
pub fn generate_commitment(
terms_data: &[u8],
schema_hash: &[u8; 32],
version: u32,
) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(AGREEMENT_COMMITMENT_SEP);
hasher.update(schema_hash);
hasher.update(&version.to_le_bytes());
hasher.update(terms_data);
*hasher.finalize().as_bytes()
}
pub fn is_fully_signed(&self) -> bool {
self.parties.iter().all(|p| p.signed)
}
pub fn signed_count(&self) -> usize {
self.parties.iter().filter(|p| p.signed).count()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SignatureType {
Single,
Threshold { threshold: u8, total: u8 },
Multi { required: u8 },
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PartySignature {
pub signature_id: SignatureId,
pub agreement_id: AgreementId,
pub party_ref: PartyRef,
pub role: AgreementRole,
pub signature_type: SignatureType,
pub signature: Vec<u8>,
pub signer_key: [u8; 32],
pub signed_at: Timestamp,
pub recorded_at_height: BlockHeight,
pub witness_attestation_id: Option<AttestationId>,
}
impl PartySignature {
pub fn generate_id(
agreement_id: &AgreementId,
party_ref: &PartyRef,
role: AgreementRole,
nonce: &[u8; 32],
) -> SignatureId {
let mut hasher = blake3::Hasher::new();
hasher.update(SIGNATURE_DOMAIN_SEP);
hasher.update(b":v1:");
hasher.update(agreement_id);
hasher.update(&party_ref.as_hash());
hasher.update(&[role as u8]);
hasher.update(nonce);
*hasher.finalize().as_bytes()
}
pub fn generate_signing_message(
agreement_id: &AgreementId,
agreement_commitment: &[u8; 32],
party_ref: &PartyRef,
role: AgreementRole,
policy_id: &PolicyId,
) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(SIGNATURE_MESSAGE_SEP);
hasher.update(agreement_id);
hasher.update(agreement_commitment);
hasher.update(&party_ref.as_hash());
hasher.update(&[role as u8]);
hasher.update(policy_id);
*hasher.finalize().as_bytes()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum AttestationIssuerClass {
NotaryPublic = 0,
LawFirm = 1,
Auditor = 2,
Cpa = 3,
CourtOfficial = 4,
GovernmentAgency = 5,
RegisteredAgent = 6,
EscrowAgent = 7,
TitleCompany = 8,
Other = 255,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum AttestationType {
Notarization = 0,
SignatureWitness = 1,
IdentityVerification = 2,
DocumentAuthentication = 3,
Apostille = 4,
Certification = 5,
Acknowledgment = 6,
Jurat = 7,
CopyCertification = 8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum AttestationStatus {
Active = 0,
Expired = 1,
Revoked = 2,
Superseded = 3,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AttestationPacket {
pub attestation_id: AttestationId,
pub target_ref: AttestationTarget,
pub issuer_address: Address,
pub issuer_class: AttestationIssuerClass,
pub attestation_type: AttestationType,
pub notary_commitment: [u8; 32],
pub jurisdiction_code: String,
pub valid_from: Timestamp,
pub expiry: Option<Timestamp>,
pub revocation_ref: Option<[u8; 32]>,
pub status: AttestationStatus,
pub created_at: Timestamp,
pub recorded_at_height: BlockHeight,
pub policy_id: PolicyId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AttestationTarget {
Agreement(AgreementId),
DocumentHash([u8; 32]),
Signature(SignatureId),
IpAction(IpAssetId),
}
impl AttestationPacket {
pub fn generate_id(
issuer: &Address,
target: &AttestationTarget,
attestation_type: AttestationType,
nonce: &[u8; 32],
) -> AttestationId {
let mut hasher = blake3::Hasher::new();
hasher.update(ATTESTATION_DOMAIN_SEP);
hasher.update(b":v1:");
hasher.update(issuer.as_ref());
match target {
AttestationTarget::Agreement(id) => {
hasher.update(b"agreement:");
hasher.update(id);
}
AttestationTarget::DocumentHash(h) => {
hasher.update(b"document:");
hasher.update(h);
}
AttestationTarget::Signature(id) => {
hasher.update(b"signature:");
hasher.update(id);
}
AttestationTarget::IpAction(id) => {
hasher.update(b"ip_action:");
hasher.update(id);
}
}
hasher.update(&[attestation_type as u8]);
hasher.update(nonce);
*hasher.finalize().as_bytes()
}
pub fn generate_notary_commitment(
commission_number: &str,
venue: &str,
procedure_hash: &[u8; 32],
) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(NOTARY_COMMITMENT_SEP);
hasher.update(commission_number.as_bytes());
hasher.update(venue.as_bytes());
hasher.update(procedure_hash);
*hasher.finalize().as_bytes()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum IpActionType {
Assignment = 0,
License = 1,
Pledge = 2,
Release = 3,
ExclusiveLicense = 4,
NonExclusiveLicense = 5,
Sublicense = 6,
LicenseTermination = 7,
PledgeTransfer = 8,
PledgeRelease = 9,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum IpAssetType {
Patent = 0,
Trademark = 1,
Copyright = 2,
TradeSecret = 3,
Design = 4,
Software = 5,
Database = 6,
Domain = 7,
Other = 255,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum IpActionStatus {
Pending = 0,
Active = 1,
Expired = 2,
Revoked = 3,
Superseded = 4,
Terminated = 5,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IpRightsAction {
pub action_id: IpAssetId,
pub ip_asset_commitment: [u8; 32],
pub asset_type: IpAssetType,
pub action_type: IpActionType,
pub scope_commitment: [u8; 32],
pub rightsholder_ref: PartyRef,
pub counterparty_ref: Option<PartyRef>,
pub policy_id: PolicyId,
pub valid_from: Timestamp,
pub expiry: Option<Timestamp>,
pub revocation_ref: Option<[u8; 32]>,
pub status: IpActionStatus,
pub created_at: Timestamp,
pub recorded_at_height: BlockHeight,
pub agreement_id: Option<AgreementId>,
pub attachments: Vec<AttachmentRef>,
}
impl IpRightsAction {
pub fn generate_id(
rightsholder: &PartyRef,
ip_asset_commitment: &[u8; 32],
action_type: IpActionType,
nonce: &[u8; 32],
) -> IpAssetId {
let mut hasher = blake3::Hasher::new();
hasher.update(IP_ACTION_DOMAIN_SEP);
hasher.update(b":v1:");
hasher.update(&rightsholder.as_hash());
hasher.update(ip_asset_commitment);
hasher.update(&[action_type as u8]);
hasher.update(nonce);
*hasher.finalize().as_bytes()
}
pub fn generate_scope_commitment(
territory: &str,
field_of_use: &str,
duration_secs: Option<u64>,
additional_terms: Option<&[u8]>,
) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(IP_SCOPE_COMMITMENT_SEP);
hasher.update(territory.as_bytes());
hasher.update(b"|");
hasher.update(field_of_use.as_bytes());
if let Some(dur) = duration_secs {
hasher.update(b"|");
hasher.update(&dur.to_le_bytes());
}
if let Some(terms) = additional_terms {
hasher.update(b"|");
hasher.update(terms);
}
*hasher.finalize().as_bytes()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum ExecutorState {
Draft = 0,
Active = 1,
Paused = 2,
Terminated = 3,
Completed = 4,
Disputed = 5,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExecutorLink {
pub link_id: ExecutorLinkId,
pub agreement_id: AgreementId,
pub executor_contract: Address,
pub executor_interface_id: [u8; 32],
pub terms_commitment: [u8; 32],
pub activation_policy_id: PolicyId,
pub state: ExecutorState,
pub created_at: Timestamp,
pub updated_at: Timestamp,
pub created_at_height: BlockHeight,
pub activation_proof_id: Option<ProofId>,
}
impl ExecutorLink {
pub fn generate_id(
agreement_id: &AgreementId,
executor_contract: &Address,
nonce: &[u8; 32],
) -> ExecutorLinkId {
let mut hasher = blake3::Hasher::new();
hasher.update(EXECUTOR_LINK_DOMAIN_SEP);
hasher.update(b":v1:");
hasher.update(agreement_id);
hasher.update(executor_contract.as_ref());
hasher.update(nonce);
*hasher.finalize().as_bytes()
}
pub fn generate_terms_commitment(
terms_data: &[u8],
executor_interface_id: &[u8; 32],
) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(TERMS_COMMITMENT_SEP);
hasher.update(executor_interface_id);
hasher.update(terms_data);
*hasher.finalize().as_bytes()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum AgreementProofProfile {
SignedByRoles = 0,
NotaryAttested = 1,
IpAssignmentValid = 2,
ExecutorBoundActive = 3,
PartyBound = 4,
AgreementStatus = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum AgreementProofType {
Mock = 0,
Groth16 = 1,
Plonk = 2,
ThresholdSignature = 3,
MerkleInclusion = 4,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AgreementProofEnvelope {
pub proof_id: ProofId,
pub profile: AgreementProofProfile,
pub profile_id: String,
pub policy_ids: Vec<PolicyId>,
pub public_inputs: Vec<u8>,
pub proof_data: Vec<u8>,
pub proof_type: AgreementProofType,
pub subject_nullifier: [u8; 32],
pub generated_at: Timestamp,
pub expires_at: Timestamp,
}
impl AgreementProofEnvelope {
pub fn generate_id(
profile: AgreementProofProfile,
subject_nullifier: &[u8; 32],
policy_ids: &[PolicyId],
nonce: &[u8; 32],
) -> ProofId {
let mut hasher = blake3::Hasher::new();
hasher.update(PROOF_PROFILE_DOMAIN_SEP);
hasher.update(b":v1:");
hasher.update(&[profile as u8]);
hasher.update(subject_nullifier);
for policy_id in policy_ids {
hasher.update(policy_id);
}
hasher.update(nonce);
*hasher.finalize().as_bytes()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum AgreementOperation {
CommitAgreement = 0,
UpdateAgreement = 1,
TerminateAgreement = 2,
VoidAgreement = 3,
SupersedeAgreement = 4,
SignAgreement = 10,
RevokeSignature = 11,
AddParty = 12,
RemoveParty = 13,
CreateAttestation = 20,
RevokeAttestation = 21,
UpdateAttestationStatus = 22,
RecordIpAction = 30,
UpdateIpAction = 31,
TerminateIpAction = 32,
RevokeIpAction = 33,
LinkExecutor = 40,
ActivateExecutor = 41,
PauseExecutor = 42,
ResumeExecutor = 43,
TerminateExecutor = 44,
CompleteExecutor = 45,
SubmitProof = 50,
VerifyProof = 51,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AgreementTxData {
pub operation: AgreementOperation,
pub data: Vec<u8>,
pub recipient: crate::Address,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AgreementEvent {
AgreementCommitted {
agreement_id: AgreementId,
policy_id: PolicyId,
commitment_hash: [u8; 32],
timestamp: Timestamp,
},
AgreementUpdated {
agreement_id: AgreementId,
new_status: AgreementStatus,
timestamp: Timestamp,
},
AgreementTerminated {
agreement_id: AgreementId,
timestamp: Timestamp,
},
AgreementSuperseded {
old_agreement_id: AgreementId,
new_agreement_id: AgreementId,
timestamp: Timestamp,
},
AgreementSigned {
agreement_id: AgreementId,
party_ref_hash: [u8; 32],
role: AgreementRole,
signer: Address,
timestamp: Timestamp,
},
SignatureRevoked {
agreement_id: AgreementId,
signature_id: SignatureId,
timestamp: Timestamp,
},
NotaryAttested {
target_id: [u8; 32],
notary: Address,
attestation_hash: [u8; 32],
timestamp: Timestamp,
},
AttestationRevoked {
attestation_id: AttestationId,
timestamp: Timestamp,
},
IpActionRecorded {
ip_asset_hash: [u8; 32],
action_type: IpActionType,
policy_id: PolicyId,
timestamp: Timestamp,
},
IpActionUpdated {
action_id: IpAssetId,
new_status: IpActionStatus,
timestamp: Timestamp,
},
ExecutorLinked {
agreement_id: AgreementId,
executor: Address,
interface_id: [u8; 32],
state: ExecutorState,
timestamp: Timestamp,
},
ExecutorStateUpdated {
agreement_id: AgreementId,
link_id: ExecutorLinkId,
new_state: ExecutorState,
timestamp: Timestamp,
},
ProofSubmitted {
proof_id: ProofId,
profile: AgreementProofProfile,
timestamp: Timestamp,
},
ProofVerified {
proof_id: ProofId,
valid: bool,
timestamp: Timestamp,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_party_ref_commitment() {
let subject = [1u8; 32];
let salt = [2u8; 32];
let commitment = PartyRef::generate_commitment(&subject, &salt);
assert_ne!(commitment, [0u8; 32]);
let salt2 = [3u8; 32];
let commitment2 = PartyRef::generate_commitment(&subject, &salt2);
assert_ne!(commitment, commitment2);
}
#[test]
fn test_agreement_id_generation() {
let creator = Address::new([1u8; 20]);
let commitment = [2u8; 32];
let nonce = [3u8; 32];
let id = AgreementCommitment::generate_id(&creator, &commitment, &nonce);
assert_ne!(id, [0u8; 32]);
let id2 = AgreementCommitment::generate_id(&creator, &commitment, &nonce);
assert_eq!(id, id2);
}
#[test]
fn test_signing_message_domain_separation() {
let agreement_id = [1u8; 32];
let commitment = [2u8; 32];
let party_ref = PartyRef::Commitment([3u8; 32]);
let policy_id = [4u8; 32];
let msg1 = PartySignature::generate_signing_message(
&agreement_id,
&commitment,
&party_ref,
AgreementRole::Buyer,
&policy_id,
);
let msg2 = PartySignature::generate_signing_message(
&agreement_id,
&commitment,
&party_ref,
AgreementRole::Seller,
&policy_id,
);
assert_ne!(msg1, msg2);
}
#[test]
fn test_attestation_id_generation() {
let issuer = Address::new([1u8; 20]);
let target = AttestationTarget::Agreement([2u8; 32]);
let nonce = [3u8; 32];
let id = AttestationPacket::generate_id(
&issuer,
&target,
AttestationType::Notarization,
&nonce,
);
assert_ne!(id, [0u8; 32]);
}
#[test]
fn test_ip_scope_commitment() {
let commitment = IpRightsAction::generate_scope_commitment(
"US",
"software",
Some(31536000),
None,
);
assert_ne!(commitment, [0u8; 32]);
}
#[test]
fn test_executor_terms_commitment() {
let terms = b"escrow terms here";
let interface = [1u8; 32];
let commitment = ExecutorLink::generate_terms_commitment(terms, &interface);
assert_ne!(commitment, [0u8; 32]);
}
}