Generic verify over a JSON-LD value — the same three checks as the typed path: (1) the content hash recomputes over `hashing_view`, (2) the signature verifies over `jcs(signing_view)`, (3) the proof's verificationMethod DID belongs to this key.
(value: &Value, public_key: &PublicKey)
| 104 | /// verifies over `jcs(signing_view)`, (3) the proof's verificationMethod DID |
| 105 | /// belongs to this key. |
| 106 | pub fn verify_value(value: &Value, public_key: &PublicKey) -> Result<()> { |
| 107 | let obj = value |
| 108 | .as_object() |
| 109 | .ok_or_else(|| CanonicalError::Proof("value is not a JSON object".into()))?; |
| 110 | |
| 111 | // 1. Content hash integrity. |
| 112 | let expected = obj |
| 113 | .get(PROP_CONTENT_HASH) |
| 114 | .and_then(Value::as_str) |
| 115 | .ok_or_else(|| CanonicalError::Proof("node carries no contentHash".into()))?; |
| 116 | let actual = hash::content_hash(&hashing_view(value)); |
| 117 | if expected != actual { |
| 118 | return Err(CanonicalError::HashMismatch { |
| 119 | expected: expected.to_string(), |
| 120 | actual, |
| 121 | }); |
| 122 | } |
| 123 | |
| 124 | // 2. Signature over the canonical signing bytes. |
| 125 | let proof: Proof = obj |
| 126 | .get(PROP_PROOF) |
| 127 | .cloned() |
| 128 | .and_then(|p| serde_json::from_value(p).ok()) |
| 129 | .ok_or_else(|| CanonicalError::Proof("node carries no proof".into()))?; |
| 130 | |
| 131 | // The proof's own metadata is stripped by `signing_view`, so it is not |
| 132 | // covered by the signature. Enforce the suite/purpose/type we expect here, |
| 133 | // otherwise a stored node's declared cryptosuite is silently mutable. |
| 134 | if proof.type_ != PROOF_TYPE |
| 135 | || proof.cryptosuite != CRYPTOSUITE |
| 136 | || proof.proof_purpose != PROOF_PURPOSE |
| 137 | { |
| 138 | return Err(CanonicalError::Verification(format!( |
| 139 | "unexpected proof metadata: type='{}' cryptosuite='{}' proofPurpose='{}'", |
| 140 | proof.type_, proof.cryptosuite, proof.proof_purpose |
| 141 | ))); |
| 142 | } |
| 143 | |
| 144 | let signature = decode_proof_value(&proof.proof_value)?; |
| 145 | let signing_bytes = jcs::canonicalize(&signing_view(value)).into_bytes(); |
| 146 | signature |
| 147 | .verify(&signing_bytes, public_key) |
| 148 | .map_err(|e| CanonicalError::Verification(e.to_string()))?; |
| 149 | |
| 150 | // 3. The proof's verificationMethod DID must belong to this key. |
| 151 | let did = did::did_from_verification_method(&proof.verification_method); |
| 152 | if !did::did_matches_public_key(did, public_key) { |
| 153 | return Err(CanonicalError::Verification( |
| 154 | "verificationMethod DID does not match the public key".into(), |
| 155 | )); |
| 156 | } |
| 157 | Ok(()) |
| 158 | } |
| 159 | |
| 160 | /// Produce a fully attested Intent node — a thin wrapper over |
| 161 | /// [`attest_value`] so hash and signature can never drift from any other node |