504 lines
14 KiB
Rust
504 lines
14 KiB
Rust
/// NAC ACC-1643 协议实现
|
||
/// 基于GNACS的文档管理与法律披露协议
|
||
|
||
mod error;
|
||
mod types;
|
||
|
||
pub use error::{Acc1643Error, Result};
|
||
pub use types::*;
|
||
|
||
use sha3::{Digest, Sha3_384};
|
||
use std::collections::HashMap;
|
||
|
||
/// ACC-1643 文档管理协议
|
||
#[derive(Debug)]
|
||
pub struct Acc1643 {
|
||
/// 文档存储(doc_id -> AssetDocument)
|
||
documents: HashMap<String, AssetDocument>,
|
||
|
||
/// 文档类型索引(doc_type -> vec<doc_id>)
|
||
type_index: HashMap<String, Vec<String>>,
|
||
|
||
/// 文档Merkle根
|
||
documents_root: [u8; 16],
|
||
|
||
/// 角色权限(简化实现)
|
||
roles: HashMap<String, Vec<String>>,
|
||
|
||
/// 合法文档类型白名单
|
||
valid_doc_types: Vec<String>,
|
||
}
|
||
|
||
impl Acc1643 {
|
||
/// 创建新的ACC-1643实例
|
||
pub fn new() -> Self {
|
||
let mut valid_types = Vec::new();
|
||
valid_types.push("Prospectus".to_string());
|
||
valid_types.push("AuditReport".to_string());
|
||
valid_types.push("LegalOpinion".to_string());
|
||
valid_types.push("FinancialStatement".to_string());
|
||
valid_types.push("ComplianceReport".to_string());
|
||
|
||
Self {
|
||
documents: HashMap::new(),
|
||
type_index: HashMap::new(),
|
||
documents_root: [0u8; 16],
|
||
roles: HashMap::new(),
|
||
valid_doc_types: valid_types,
|
||
}
|
||
}
|
||
|
||
/// 设置文档(首次上传或更新)
|
||
pub fn set_document(
|
||
&mut self,
|
||
operator: &str,
|
||
doc_type: String,
|
||
uri: String,
|
||
content_hash: [u8; 48],
|
||
supersedes: [u8; 48],
|
||
receipt_hash: [u8; 32],
|
||
) -> Result<[u8; 48]> {
|
||
// 检查权限
|
||
if !self.has_role("ISSUER", operator) {
|
||
return Err(Acc1643Error::Unauthorized {
|
||
operator: operator.to_string(),
|
||
required_role: "ISSUER".to_string(),
|
||
});
|
||
}
|
||
|
||
// 验证文档类型
|
||
if !self.valid_doc_types.contains(&doc_type) {
|
||
return Err(Acc1643Error::InvalidDocumentType { doc_type });
|
||
}
|
||
|
||
// 验证宪法收据(简化实现)
|
||
if receipt_hash == [0u8; 32] {
|
||
return Err(Acc1643Error::InvalidConstitutionalReceipt {
|
||
receipt_hash: hex::encode(receipt_hash),
|
||
});
|
||
}
|
||
|
||
// 计算文档ID
|
||
let doc_id = self.calculate_doc_id(&uri, &content_hash, Self::current_timestamp());
|
||
let doc_id_hex = hex::encode(doc_id);
|
||
|
||
// 检查是否已存在
|
||
if self.documents.contains_key(&doc_id_hex) {
|
||
return Err(Acc1643Error::DocumentAlreadyExists { doc_id: doc_id_hex });
|
||
}
|
||
|
||
// 确定版本号
|
||
let version = if supersedes == [0u8; 48] {
|
||
1
|
||
} else {
|
||
let supersedes_hex = hex::encode(supersedes);
|
||
if let Some(old_doc) = self.documents.get(&supersedes_hex) {
|
||
old_doc.version + 1
|
||
} else {
|
||
return Err(Acc1643Error::DocumentNotFound {
|
||
doc_id: supersedes_hex,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 创建文档
|
||
let document = AssetDocument {
|
||
doc_id,
|
||
doc_type: doc_type.clone(),
|
||
uri,
|
||
content_hash,
|
||
timestamp: Self::current_timestamp(),
|
||
version,
|
||
supersedes,
|
||
is_active: true,
|
||
};
|
||
|
||
// 存储文档
|
||
self.documents.insert(doc_id_hex.clone(), document);
|
||
|
||
// 更新类型索引
|
||
self.type_index
|
||
.entry(doc_type)
|
||
.or_insert_with(Vec::new)
|
||
.push(doc_id_hex);
|
||
|
||
// 更新Merkle根
|
||
self.update_documents_root();
|
||
|
||
Ok(doc_id)
|
||
}
|
||
|
||
/// 批量设置文档(原子操作)
|
||
pub fn set_documents(
|
||
&mut self,
|
||
operator: &str,
|
||
inputs: Vec<DocumentInput>,
|
||
receipt_hash: [u8; 32],
|
||
) -> Result<Vec<[u8; 48]>> {
|
||
let mut doc_ids = Vec::new();
|
||
|
||
for input in inputs {
|
||
let doc_id = self.set_document(
|
||
operator,
|
||
input.doc_type,
|
||
input.uri,
|
||
input.content_hash,
|
||
input.supersedes,
|
||
receipt_hash,
|
||
)?;
|
||
doc_ids.push(doc_id);
|
||
}
|
||
|
||
Ok(doc_ids)
|
||
}
|
||
|
||
/// 移除文档(标记为无效)
|
||
pub fn remove_document(
|
||
&mut self,
|
||
operator: &str,
|
||
doc_id: &[u8; 48],
|
||
receipt_hash: [u8; 32],
|
||
) -> Result<()> {
|
||
// 检查权限
|
||
if !self.has_role("ISSUER", operator) {
|
||
return Err(Acc1643Error::Unauthorized {
|
||
operator: operator.to_string(),
|
||
required_role: "ISSUER".to_string(),
|
||
});
|
||
}
|
||
|
||
// 验证宪法收据
|
||
if receipt_hash == [0u8; 32] {
|
||
return Err(Acc1643Error::InvalidConstitutionalReceipt {
|
||
receipt_hash: hex::encode(receipt_hash),
|
||
});
|
||
}
|
||
|
||
let doc_id_hex = hex::encode(doc_id);
|
||
|
||
// 查找文档
|
||
let document = self.documents.get_mut(&doc_id_hex).ok_or_else(|| {
|
||
Acc1643Error::DocumentNotFound {
|
||
doc_id: doc_id_hex.clone(),
|
||
}
|
||
})?;
|
||
|
||
// 标记为无效
|
||
document.is_active = false;
|
||
|
||
// 更新Merkle根
|
||
self.update_documents_root();
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 获取文档详情
|
||
pub fn get_document(&self, doc_id: &[u8; 48]) -> Result<AssetDocument> {
|
||
let doc_id_hex = hex::encode(doc_id);
|
||
self.documents
|
||
.get(&doc_id_hex)
|
||
.cloned()
|
||
.ok_or_else(|| Acc1643Error::DocumentNotFound { doc_id: doc_id_hex })
|
||
}
|
||
|
||
/// 获取指定类型的最新文档
|
||
pub fn get_latest_document(&self, doc_type: &str) -> Result<AssetDocument> {
|
||
let doc_ids = self.type_index.get(doc_type).ok_or_else(|| {
|
||
Acc1643Error::InvalidDocumentType {
|
||
doc_type: doc_type.to_string(),
|
||
}
|
||
})?;
|
||
|
||
let mut latest: Option<AssetDocument> = None;
|
||
|
||
for doc_id_hex in doc_ids {
|
||
if let Some(doc) = self.documents.get(doc_id_hex) {
|
||
if doc.is_active {
|
||
if let Some(ref current_latest) = latest {
|
||
if doc.version > current_latest.version {
|
||
latest = Some(doc.clone());
|
||
}
|
||
} else {
|
||
latest = Some(doc.clone());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
latest.ok_or_else(|| Acc1643Error::DocumentNotFound {
|
||
doc_id: format!("latest of type {}", doc_type),
|
||
})
|
||
}
|
||
|
||
/// 获取所有文档ID(分页)
|
||
pub fn get_all_documents(&self, offset: usize, limit: usize) -> Vec<[u8; 48]> {
|
||
self.documents
|
||
.values()
|
||
.skip(offset)
|
||
.take(limit)
|
||
.map(|doc| doc.doc_id)
|
||
.collect()
|
||
}
|
||
|
||
/// 验证文档是否属于资产且未被篡改
|
||
pub fn verify_document(
|
||
&self,
|
||
doc_id: &[u8; 48],
|
||
uri: &str,
|
||
content_hash: &[u8; 48],
|
||
) -> Result<bool> {
|
||
let document = self.get_document(doc_id)?;
|
||
|
||
// 验证URI和内容哈希
|
||
if document.uri != uri {
|
||
return Ok(false);
|
||
}
|
||
|
||
if &document.content_hash != content_hash {
|
||
return Ok(false);
|
||
}
|
||
|
||
// 验证文档ID
|
||
let expected_id = self.calculate_doc_id(uri, content_hash, document.timestamp);
|
||
if expected_id != *doc_id {
|
||
return Ok(false);
|
||
}
|
||
|
||
Ok(true)
|
||
}
|
||
|
||
/// 获取文档Merkle根
|
||
pub fn documents_root(&self) -> [u8; 16] {
|
||
self.documents_root
|
||
}
|
||
|
||
/// 计算文档ID(SHA3-384(uri+contentHash+timestamp))
|
||
fn calculate_doc_id(&self, uri: &str, content_hash: &[u8; 48], timestamp: u64) -> [u8; 48] {
|
||
let mut hasher = Sha3_384::new();
|
||
hasher.update(uri.as_bytes());
|
||
hasher.update(content_hash);
|
||
hasher.update(×tamp.to_be_bytes());
|
||
|
||
let result = hasher.finalize();
|
||
let mut doc_id = [0u8; 48];
|
||
doc_id.copy_from_slice(&result);
|
||
doc_id
|
||
}
|
||
|
||
/// 更新文档Merkle根
|
||
fn update_documents_root(&mut self) {
|
||
// 简化实现:对所有文档ID进行哈希
|
||
let mut hasher = Sha3_384::new();
|
||
|
||
let mut doc_ids: Vec<_> = self.documents.keys().collect();
|
||
doc_ids.sort();
|
||
|
||
for doc_id in doc_ids {
|
||
hasher.update(doc_id.as_bytes());
|
||
}
|
||
|
||
let result = hasher.finalize();
|
||
self.documents_root[..16].copy_from_slice(&result[..16]);
|
||
}
|
||
|
||
/// 检查角色权限
|
||
fn has_role(&self, role: &str, account: &str) -> bool {
|
||
if let Some(accounts) = self.roles.get(role) {
|
||
accounts.contains(&account.to_string())
|
||
} else {
|
||
false
|
||
}
|
||
}
|
||
|
||
/// 授予角色
|
||
pub fn grant_role(&mut self, role: &str, account: &str) {
|
||
self.roles
|
||
.entry(role.to_string())
|
||
.or_insert_with(Vec::new)
|
||
.push(account.to_string());
|
||
}
|
||
|
||
/// 获取当前时间戳
|
||
fn current_timestamp() -> u64 {
|
||
std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs()
|
||
}
|
||
}
|
||
|
||
impl Default for Acc1643 {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
fn create_test_acc1643() -> Acc1643 {
|
||
let mut acc1643 = Acc1643::new();
|
||
acc1643.grant_role("ISSUER", "issuer1");
|
||
acc1643
|
||
}
|
||
|
||
#[test]
|
||
fn test_set_document() {
|
||
let mut acc1643 = create_test_acc1643();
|
||
|
||
let content_hash = [1u8; 48];
|
||
let doc_id = acc1643
|
||
.set_document(
|
||
"issuer1",
|
||
"Prospectus".to_string(),
|
||
"ipfs://QmTest".to_string(),
|
||
content_hash,
|
||
[0u8; 48],
|
||
[1u8; 32],
|
||
)
|
||
.unwrap();
|
||
|
||
let document = acc1643.get_document(&doc_id).unwrap();
|
||
assert_eq!(document.doc_type, "Prospectus");
|
||
assert_eq!(document.version, 1);
|
||
assert!(document.is_active);
|
||
}
|
||
|
||
#[test]
|
||
fn test_document_versioning() {
|
||
let mut acc1643 = create_test_acc1643();
|
||
|
||
// 创建第一个版本
|
||
let content_hash_v1 = [1u8; 48];
|
||
let doc_id_v1 = acc1643
|
||
.set_document(
|
||
"issuer1",
|
||
"Prospectus".to_string(),
|
||
"ipfs://QmTest1".to_string(),
|
||
content_hash_v1,
|
||
[0u8; 48],
|
||
[1u8; 32],
|
||
)
|
||
.unwrap();
|
||
|
||
// 创建第二个版本
|
||
let content_hash_v2 = [2u8; 48];
|
||
let doc_id_v2 = acc1643
|
||
.set_document(
|
||
"issuer1",
|
||
"Prospectus".to_string(),
|
||
"ipfs://QmTest2".to_string(),
|
||
content_hash_v2,
|
||
doc_id_v1,
|
||
[1u8; 32],
|
||
)
|
||
.unwrap();
|
||
|
||
let doc_v2 = acc1643.get_document(&doc_id_v2).unwrap();
|
||
assert_eq!(doc_v2.version, 2);
|
||
assert_eq!(doc_v2.supersedes, doc_id_v1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_remove_document() {
|
||
let mut acc1643 = create_test_acc1643();
|
||
|
||
let content_hash = [1u8; 48];
|
||
let doc_id = acc1643
|
||
.set_document(
|
||
"issuer1",
|
||
"Prospectus".to_string(),
|
||
"ipfs://QmTest".to_string(),
|
||
content_hash,
|
||
[0u8; 48],
|
||
[1u8; 32],
|
||
)
|
||
.unwrap();
|
||
|
||
acc1643.remove_document("issuer1", &doc_id, [1u8; 32]).unwrap();
|
||
|
||
let document = acc1643.get_document(&doc_id).unwrap();
|
||
assert!(!document.is_active);
|
||
}
|
||
|
||
#[test]
|
||
fn test_get_latest_document() {
|
||
let mut acc1643 = create_test_acc1643();
|
||
|
||
// 创建多个版本
|
||
let doc_id_v1 = acc1643
|
||
.set_document(
|
||
"issuer1",
|
||
"Prospectus".to_string(),
|
||
"ipfs://QmTest1".to_string(),
|
||
[1u8; 48],
|
||
[0u8; 48],
|
||
[1u8; 32],
|
||
)
|
||
.unwrap();
|
||
|
||
let _doc_id_v2 = acc1643
|
||
.set_document(
|
||
"issuer1",
|
||
"Prospectus".to_string(),
|
||
"ipfs://QmTest2".to_string(),
|
||
[2u8; 48],
|
||
doc_id_v1,
|
||
[1u8; 32],
|
||
)
|
||
.unwrap();
|
||
|
||
let latest = acc1643.get_latest_document("Prospectus").unwrap();
|
||
assert_eq!(latest.version, 2);
|
||
}
|
||
|
||
#[test]
|
||
fn test_verify_document() {
|
||
let mut acc1643 = create_test_acc1643();
|
||
|
||
let content_hash = [1u8; 48];
|
||
let uri = "ipfs://QmTest".to_string();
|
||
|
||
let doc_id = acc1643
|
||
.set_document(
|
||
"issuer1",
|
||
"Prospectus".to_string(),
|
||
uri.clone(),
|
||
content_hash,
|
||
[0u8; 48],
|
||
[1u8; 32],
|
||
)
|
||
.unwrap();
|
||
|
||
let is_valid = acc1643.verify_document(&doc_id, &uri, &content_hash).unwrap();
|
||
assert!(is_valid);
|
||
|
||
// 验证错误的内容哈希
|
||
let wrong_hash = [2u8; 48];
|
||
let is_valid = acc1643.verify_document(&doc_id, &uri, &wrong_hash).unwrap();
|
||
assert!(!is_valid);
|
||
}
|
||
|
||
#[test]
|
||
fn test_documents_root_update() {
|
||
let mut acc1643 = create_test_acc1643();
|
||
|
||
let root_before = acc1643.documents_root();
|
||
|
||
acc1643
|
||
.set_document(
|
||
"issuer1",
|
||
"Prospectus".to_string(),
|
||
"ipfs://QmTest".to_string(),
|
||
[1u8; 48],
|
||
[0u8; 48],
|
||
[1u8; 32],
|
||
)
|
||
.unwrap();
|
||
|
||
let root_after = acc1643.documents_root();
|
||
assert_ne!(root_before, root_after);
|
||
}
|
||
}
|