DEV Community

Daniel Ioni
Daniel Ioni

Posted on

We Put a Knowledge Card on Ethereum. Then We Realized the Hash Was the Easy Part.

We Put a Knowledge Card on Ethereum. Then We Realized the Hash Was the Easy Part.
When we started experimenting with verifiable knowledge inside MyZubster, the first idea seemed straightforward:
Take a public Knowledge Card, hash it, put the hash on-chain, and now you have proof.
Technically, that worked.
Conceptually, it was incomplete.
The experiment eventually led us to a more interesting problem:
How do you represent the relationship between a person, a claim, the evidence supporting it, its exact cryptographic representation, and a blockchain attestation — without pretending that any of those things proves the claim is true?
That question led to MyZubster Evidence Graph v1.
This article describes the experiment, including a mistake in our first blockchain proof, the corrected Proof v2, and the evidence graph architecture that came out of it.
The starting point: a real software contribution
The experiment started with a real Knowledge Card associated with the contributor N4K48.
The work involved a local stack using Docker Compose, Ollama, Qdrant, an API and Open WebUI.
During testing, a problem was found in the document chunking logic. The ingestion script was modified to better respect paragraph and line boundaries while preserving overlap and avoiding loops.
After re-ingestion, the system loaded:

  • 46 knowledge chunks into Qdrant
  • 1 empty document skipped The work was recorded in a MyZubster Knowledge Card. That gave us something more interesting than a synthetic blockchain demo: a concrete software contribution with artifacts behind it. But then came the question: What exactly should we put on-chain? Proof v1: hashing the URL Our first experiment deployed a small smart contract on Ethereum Sepolia. The contract stored a bytes32 value together with information such as the creator and block timestamp. For the first proof, the stored value was derived from the URL of the Knowledge Card. That produced a valid blockchain transaction. But it exposed an important limitation. Suppose the URL is: https://www.myzubster.com/knowledge-card?id=...

Hashing that string proves that a particular URL string was used.
It does not cryptographically commit to the complete contents displayed at that URL.
The contents could change while the URL remained identical.
So although Proof v1 was technically valid, its semantics were weaker than we wanted.
That became the motivation for Proof v2.
Proof v2: hash the exact content
Instead of hashing a URL, we created a canonical representation of the Knowledge Card.
For this experiment the canonical payload was stored as:
proofs/knowledge-card-6abaaefb3a7460c4574a45fd-v1.json

The exact committed file is 3168 bytes.
We then calculated SHA-256 over those exact bytes.
The resulting digest was:
6097e05866bafceec24663d2638cb1dae5742ac78284abbfd45cc9c3b0bfb845

This digest was placed in a new MyZubsterProof contract on Ethereum Sepolia.
Network:
Ethereum Sepolia
chainId: 11155111

Contract:
0x21787249Df054132093FcF09bB914C0CCC539390

Deployment transaction:
0xc837ba3f3046f3712e3eba4b81107cb22939b61300f2cab3cfe5bbb7b3319ded

Calling knowledgeHash() on the deployed contract returns:
0x6097e05866bafceec24663d2638cb1dae5742ac78284abbfd45cc9c3b0bfb845

Now we had something much stronger.
Not proof that the Knowledge Card was true.
Proof that the exact canonical bytes committed in the repository correspond to the digest recorded by the contract.
Reproducing the proof
One of the requirements for Proof v2 was that verification should not depend on trusting MyZubster.
We retrieved the exact payload from its Git commit and calculated SHA-256 again.
The result was:
expected:
6097e05866bafceec24663d2638cb1dae5742ac78284abbfd45cc9c3b0bfb845

actual:
6097e05866bafceec24663d2638cb1dae5742ac78284abbfd45cc9c3b0bfb845

RESULT: MATCH

This gives us a reproducible chain:
Git commit
│
▼
exact canonical JSON
3168 bytes
│
│ SHA-256
▼
6097e05866bafceec24663d2638cb1dae5742ac78284abbfd45cc9c3b0bfb845
│
▼
Ethereum Sepolia
MyZubsterProof
│
▼
knowledgeHash()
│
▼
0x6097e05866bafceec24663d2638cb1dae5742ac78284abbfd45cc9c3b0bfb845

Anyone with the committed file can perform the same computation.
That was the point where another problem became obvious.
We had cryptographic integrity.
But we still didn't have a good representation of meaning.
A hash doesn't tell you what it means
Consider a digest stored on a blockchain.
What does it establish?
Potentially:
These exact bytes correspond to this digest, and this digest was recorded by this contract.

But it does not automatically establish:
The statements contained in those bytes are true.

And it certainly does not automatically establish:
This person possesses this professional skill.

Those are completely different propositions.
This distinction matters when dealing with professional reputation, portfolios, credentials and evidence.
So instead of trying to make the blockchain proof say more than it actually says, we started modeling those relationships explicitly.
That became Evidence Graph v1.
Evidence Graph v1
The basic idea is to represent evidence as typed nodes connected by relationships with deliberately constrained semantics.
A simplified graph looks like this:
Person
│
│ CLAIMS
▼
Claim
│
│ DESCRIBED_BY
▼
Knowledge Card
│
│ SUPPORTED_BY
▼
Evidence
│
│ CANONICALIZED_AS
▼
Canonical Payload
│
│ HASHED_AS
▼
Digest
│
│ ATTESTED_BY
▼
Blockchain Attestation

The important part isn't the graph itself.
It's what each edge is — and is not — allowed to mean.
CLAIMS
A person can be associated with a claim.
For example:
N4K48
│
│ CLAIMS
▼
"Contributed improvements to a knowledge ingestion workflow"

This records a claim.
It does not mark the claim as independently verified.
DESCRIBED_BY
A claim can be represented by a Knowledge Card:
Claim
│
│ DESCRIBED_BY
▼
Knowledge Card

Again, this relationship doesn't certify the claim.
It says that this Knowledge Card describes it.
SUPPORTED_BY
Evidence can be associated with the claim or Knowledge Card.
Knowledge Card
│
│ SUPPORTED_BY
▼
Evidence

An evidence reference is still not synonymous with truth.
It tells us where the supporting material is.
CANONICALIZED_AS
Now we move from semantic relationships to integrity relationships.
Evidence
│
│ CANONICALIZED_AS
▼
Canonical Payload

This represents the deterministic form used for cryptographic processing.
That distinction is important.
The human-readable object and the bytes being hashed should not be silently treated as the same thing.
HASHED_AS
The canonical payload produces a cryptographic digest:
Canonical Payload
│
│ HASHED_AS
▼
SHA-256 Digest

This relationship is reproducible.
Given the same canonical representation, another party should be able to calculate the same digest.
ATTESTED_BY
Finally:
Digest
│
│ ATTESTED_BY
▼
Blockchain Attestation

We deliberately gave this relationship narrow semantics.
ATTESTED_BY means that the digest has a confirmed blockchain anchor.
It does not mean:
PROVES_TRUTH

In fact, Evidence Graph v1 does not allow a PROVES_TRUTH relationship.
That was intentional.
Fail closed rather than invent proof
One design decision became particularly important during implementation.
If an anchor isn't confirmed, Evidence Graph must not create an attestation node.
For example:
NOT_REQUESTED
NOT_CONFIGURED
SUBMITTED
FAILED

do not become:
ATTESTED_BY

Only a confirmed anchor can produce that relationship.
Our tests explicitly check this behavior.
A real Evidence Graph
We then generated an Evidence Graph for the N4K48 Knowledge Card.
The result contained:
7 nodes
7 edges
0 attestation nodes

The nodes included:
person
claim
knowledge-card
evidence
artifact
canonical-payload
digest

And the relationships included:
CLAIMS
SUPPORTED_BY
DESCRIBED_BY
CANONICALIZED_AS
HASHED_AS

Why zero blockchain attestations?
Because the Evidence Graph evidence payload and the earlier Proof v2 canonical Knowledge Card payload are different cryptographic objects.
The Evidence Graph generated:
evidenceHash:

51483fbac73ec183d2f882428fd2ec823ec88eaa82a07b3a9cd2fe3324ec618b

Proof v2 used:
6097e05866bafceec24663d2638cb1dae5742ac78284abbfd45cc9c3b0bfb845

Those hashes should not be forced together.
Doing so would make the graph look more impressive while making it less correct.
So Evidence Graph v1 leaves the attestation absent.
Evidence hash vs graph hash
Another distinction emerged during the experiment.
The generated Knowledge Evidence had this hash:
51483fbac73ec183d2f882428fd2ec823ec88eaa82a07b3a9cd2fe3324ec618b

The resulting Evidence Graph had:
graphHash:

7dffe770d139da4a0beaf27d0b2d7e72a03bf26c5cd2180c93e1741fa8d3625e

These hashes protect different things.
The evidenceHash commits to the canonical Knowledge Evidence payload.
The graphHash protects the integrity of the Evidence Graph representation.
Therefore:
graphHash != evidenceHash

is not an error.
It is an architectural distinction.
Determinism
We also wanted the graph itself to be reproducible.
Given the same evidence and the same Knowledge Card reference, graph construction should produce the same graph hash.
The tests therefore build the graph twice and assert:
expect(second.graphHash).toBe(first.graphHash);

This gives us another useful property:
same input
↓
same semantic projection
↓
same canonical graph
↓
same graphHash

The API is intentionally boring
Evidence Graph v1 exposes a projection endpoint:
POST /api/knowledge-evidence/graph

One of the most important properties of that endpoint is what it doesn't do.
It does not automatically:
persist data
publish evidence
send a blockchain transaction
independently verify the claim

Its response explicitly communicates this:
{
"success": true,
"persisted": false,
"publication_performed": false,
"independently_verified": false
}

The endpoint is a projection operation.
That's deliberate.
We didn't want “generate a graph” to silently become “publish something about a person” or “send a transaction”.
Knowledge Cards don't change the evidence hash
Another constraint was important.
We wanted to associate an existing Knowledge Card with Evidence Graph without changing the cryptographic commitment of the underlying evidence.
So Knowledge Card composition happens at the graph layer.
Conceptually:
┌── DESCRIBED_BY ── Knowledge Card
│ │
Person ─ CLAIMS ─ Claim │
│ │
└──── SUPPORTED_BY ◄─────┘
│
Evidence

The original evidence payload remains untouched.
That means adding a Knowledge Card reference to the graph doesn't rewrite the historical evidence hash.
Tests became part of the semantics
At the time of writing, the Evidence Graph work has a focused test gate of:
3 test suites
18 tests
18 passed

The tests cover more than implementation mechanics.
They enforce semantic boundaries.
Among other things, they verify that:
unknown relations are rejected

PROVES_TRUTH is rejected

tampered canonical payloads are rejected

SUBMITTED anchors do not become attestations

confirmed anchors can produce ATTESTED_BY

Knowledge Card composition doesn't change evidenceHash

graph generation is deterministic

That last category is particularly important to us.
If this system is going to represent evidence, invalid states shouldn't merely be discouraged in documentation.
Where practical, they should be difficult to express in code.
What did the blockchain actually add?
After going through Proof v1 and Proof v2, our answer became narrower than where we started.
And that's probably a good thing.
Blockchain did not tell us whether N4K48's contribution was valuable.
It didn't determine whether the person is a good developer.
It didn't prove every statement inside the Knowledge Card.
What it gave us was something much more specific:
an independently observable place where a cryptographic digest can be anchored.
For Proof v2, we can demonstrate:
exact committed payload
│
│ SHA-256
▼
6097e058...
│
│ equals
▼
knowledgeHash()
on Ethereum Sepolia

That's useful.
But it is useful because we can describe precisely what it means.
Integrity is not truth
This became the central lesson of the experiment.
Suppose somebody writes:
Alice built a distributed database.

We canonicalize that statement and calculate its hash.
We then store the hash on Ethereum.
Ethereum can help us establish that a particular digest was anchored.
It cannot determine whether Alice actually built the database.
Those are different layers:
Claim
↓
Evidence
↓
Integrity
↓
Attestation

None should silently collapse into the next.
Why model this as a graph?
Because real evidence rarely forms a simple chain.
A claim may have multiple artifacts.
An artifact may support multiple claims.
A Knowledge Card may reference repository commits, local test results and other records.
Different cryptographic representations may exist for the same human-readable object.
And multiple attestation mechanisms could eventually coexist.
A graph gives us room for structures such as:
┌── Git commit
│
Claim ── Knowledge Card ─┼── Test result
│
└── Artifact
│
▼
Canonical Payload
│
▼
Hash
┌────┴────┐
▼ ▼
EAS Ethereum

We haven't implemented that complete architecture.
But Evidence Graph v1 gives us the vocabulary to explore it without changing the meaning of existing evidence.
What we deliberately didn't build yet
There are several obvious next steps:

  • persistent Evidence Graph storage
  • graph visualization in the UI
  • Ethereum Attestation Service integration
  • W3C Verifiable Credential mappings
  • Open Badges mappings
  • additional independent verification mechanisms
  • richer artifact provenance
  • multiple attestations for the same digest We intentionally stopped before implementing them. The purpose of v1 was to establish the semantic foundation first. The experiment changed our question We started with: How can we put proof of someone's work on a blockchain?

That question turned out to be too broad.
The better questions were:
What exactly are we proving?

Which bytes are being hashed?

What relationship does an artifact have to a claim?

Who made the claim?

Was the evidence independently verified?

Was the digest actually anchored?

Does the attestation establish integrity, provenance, identity, or truth?

Once those questions are separated, the architecture becomes much easier to reason about.
The result
The experiment now has two complementary proof chains.
Evidence Graph v1
Person
↓ CLAIMS
Claim
↓ DESCRIBED_BY
Knowledge Card
↓ SUPPORTED_BY
Evidence
↓ CANONICALIZED_AS
Canonical Payload
↓ HASHED_AS
Digest

And separately, for the existing N4K48 Proof v2:
Knowledge Card
↓
canonical Knowledge Card payload
↓ SHA-256
6097e058...
↓
Ethereum Sepolia
↓
MyZubsterProof.knowledgeHash()

The common object is the Knowledge Card.
The cryptographic objects are intentionally not pretended to be identical.
Final takeaway
The most useful result of this experiment wasn't putting a hash on Ethereum.
That part was relatively easy.
The difficult part was deciding what the hash meant.
Our current working principle is:
A blockchain attestation can establish a cryptographic relationship. It should not silently be promoted into a statement of truth.

Evidence Graph v1 is our attempt to encode that principle into the architecture itself.
Instead of:
blockchain = proof

we now think in terms of:
claim
+
evidence
+
canonicalization
+
cryptographic integrity
+
attestation
+
explicit verification status

Each layer answers a different question.
And keeping those questions separate may be more valuable than the blockchain transaction itself.
Reproducible artifacts
The Evidence Graph implementation is currently available in PR #17:
MyZubster Evidence Graph v1 — PR #17
The Proof v2 canonical payload is preserved in commit:
Canonical Knowledge Card payload commit
The Proof v2 documentation is preserved in:
Sepolia Proof v2 documentation commit
The software contribution that improved the chunking logic is available here:
Chunking improvement commit
And the Proof v2 contract and deployment transaction are publicly inspectable on Ethereum Sepolia:
MyZubsterProof v2 contract on Sepolia Etherscan
Proof v2 deployment transaction

Top comments (0)