Skip to content

Commit

Permalink
chore: adjust error handling (#2)
Browse files Browse the repository at this point in the history
* chore: adjust error handling

* test: add missing parameter for proof verification
  • Loading branch information
strumswell authored Aug 23, 2024
1 parent 95e4b32 commit 8d8b022
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 116 deletions.
127 changes: 60 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,34 @@ This library provides a simple interface for anchoring timestamps on the Ethereu

## Usage

To timestamp newly created data, you'll need to initialize the TimestampController by providing it with the leaves (your data) and the necessary encoding options. The library will then automatically generate a Merkle tree from your data.
To timestamp newly created data, you'll need to initialize the `TimestampController` by providing it with the leaves (your data) and the necessary encoding options. The library will then automatically generate a Merkle tree from your data.

Once you've created the timestamp, the next step is to anchor the Merkle tree's root hash to the Ethereum blockchain. This is done using the anchorRootHash method, which writes the root hash to the Ethereum blockchain's Trusted Hint Registry using the provided signer. During this process, an event is emitted that serves as proof of the data's existence at that specific point in time.
Once you've created the timestamp, the next step is to anchor the Merkle tree's root hash to the Ethereum blockchain. This is done using the `anchorRootHash` method, which writes the root hash to the Ethereum blockchain's Trusted Hint Registry using the provided signer. During this process, an event is emitted that serves as proof of the data's existence at that specific point in time.

To verify the integrity of your data later on, you can use the verifyProof method. This method requires you to provide the leaf (your data) and the corresponding Merkle proof. The verification process confirms that the data hasn't been altered since it was timestamped and anchored to the blockchain.
To verify the integrity of your data later on, you can use the `verifyProof` method. This method requires you to provide the leaf (your data) and the corresponding Merkle proof. The verification process confirms that the data hasn't been altered since it was timestamped and anchored to the blockchain.

For future verifications, you'll need to generate proofs. You can do this using either the getMerkleProof method for a single proof or the getAllMerkleProofs method if you need multiple proofs. It's highly recommended to store these proofs, along with their corresponding root hash, in a database. This practice ensures that you have easy access to the information needed for future verifications.
For future verifications, you'll need to generate proofs. You can do this using either the `getMerkleProof` method for a single proof or the `getAllMerkleProofs` method if you need multiple proofs. It's highly recommended to store these proofs, along with their corresponding root hash. This practice ensures that you have easy access to the information needed for future verifications.

```typescript
import { TimestampController } from "@spherity/timestamp";
import { JsonRpcProvider, Wallet, keccak256 } from "ethers";

// Initialize provider and signer
const provider = new JsonRpcProvider("https://infura.io/v3/YOUR-PROJECT-ID");
const signer = new Wallet("YOUR-PRIVATE-KEY", provider);

// Define contract options
const contractOptions = {
contractAddress: "0x1234567890123456789012345678901234567890", // Trusted Hint Registry contract address
namespace: await signer.getAddress(), // Use signer's address as namespace
list: keccak256(Buffer.from("timestampingList")); // Unique identifier for your list
};

// Define tree options with verifiable credentials
const treeOptions = {
leaves: [
[
async function main() {
// Initialize provider
const provider = new JsonRpcProvider("https://...");
// Get signer
const signer = await provider.getSigner();

// Define contract options
const contractOptions = {
contractAddress: "0x1234567890123456789012345678901234567890", // Trusted Hint Registry contract address
namespace: await signer.getAddress(), // Use signer's address as namespace
list: keccak256(Buffer.from("timestampingList")), // Unique identifier for your list
};

// Define tree options with verifiable credentials
const treeOptions = {
leaves: [
JSON.stringify({
"@context": ["https://www.w3.org/2018/credentials/v1"],
type: ["VerifiableCredential"],
Expand All @@ -41,8 +42,6 @@ const treeOptions = {
name: "Alice",
},
}),
],
[
JSON.stringify({
"@context": ["https://www.w3.org/2018/credentials/v1"],
type: ["VerifiableCredential"],
Expand All @@ -54,56 +53,55 @@ const treeOptions = {
},
}),
],
],
encoding: ["string"],
};
encoding: "string",
};

// Create TimestampController instance
const controller = new TimestampController(
signer,
contractOptions,
treeOptions
);

// Create TimestampController instance
const controller = new TimestampController(signer, contractOptions, treeOptions);
// Anchor root hash
async function anchorRootHash() {
const tx = await controller.anchorRootHash();
console.log("Root hash anchored:", tx.hash);
}

// Anchor root hash
async function anchorRootHash() {
const tx = await controller.anchorRootHash();
console.log("Root hash anchored:", tx.hash);
}

// Get and verify merkle proof
async function verifyProof() {
const leaf = [
JSON.stringify({
// Get and verify merkle proof
async function verifyProof() {
const leaf = JSON.stringify({
"@context": ["https://www.w3.org/2018/credentials/v1"],
"type": ["VerifiableCredential"],
"issuer": "did:example:123",
"issuanceDate": "2023-06-15T00:00:00Z",
"credentialSubject": {
"id": "did:example:456",
"name": "Alice"
}
})
];
const merkle = controller.getMerkleProof(leaf);
const verified = await controller.verifyProof(
leaf,
merkle.proof,
new Date("2023-06-15T00:00:00Z"), // Date when the leaf was created
7 * 24 * 3600 // Max time difference in seconds between leaf creation and timestamp of anchoring
);
console.log("Proof verified:", verified);
}

// Run example
(async () => {
type: ["VerifiableCredential"],
issuer: "did:example:123",
issuanceDate: "2023-06-15T00:00:00Z",
credentialSubject: {
id: "did:example:456",
name: "Alice",
},
});
const merkle = controller.getMerkleProof(leaf);
const verified = await controller.verifyProof(
leaf,
merkle.proof,
new Date("2023-06-15T00:00:00Z"), // Date when the leaf was created
7 * 24 * 3600 // Max time difference in seconds between leaf creation and timestamp of anchoring
);
console.log("Proof verified:", verified);
}

// Run example
await anchorRootHash();
await verifyProof();
})();

}
```

In case you want to verify a merkle proof later on, you can construct the TimestampController with the same contract options and an already anchored root hash.

```typescript
// Create TimestampController instance with an existing root hash
const existingRootHash = "0x1234567890..."; // Replace with actual root hash
const existingRootHash = "0x1234567890...";
const controllerWithExistingRoot = new TimestampController(
provider,
contractOptions,
Expand All @@ -124,18 +122,13 @@ async function verifyExistingProof() {
},
}),
];
const proof = ["0xabcdef...", "0x123456..."]; // Replace with actual proof
const proof = ["0xabcdef...", "0x123456..."];
const verified = await controllerWithExistingRoot.verifyProof(
leaf,
proof,
new Date("2023-06-20T00:00:00Z"), // Date when the leaf was created
7 * 24 * 3600 // Max time difference in seconds
7 * 24 * 3600 // Max time difference in seconds between leaf creation and timestamp of anchoring
);
console.log("Existing proof verified:", verified);
}

// Run verification with existing root hash
(async () => {
await verifyExistingProof();
})();
```
30 changes: 13 additions & 17 deletions src/TimestampController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,7 @@ class TimestampController {
* @throws {TimestampControllerError} If no root hash is available or if anchoring fails.
*/
async anchorRootHash(): Promise<ContractTransactionResponse> {
if (!this.rootHash) {
throw new TimestampControllerError(
"No merkle tree or root hash available to anchor."
);
}

const key = this.rootHash;
const key = this.getRootHash();
const value =
"0x1000000000000000000000000000000000000000000000000000000000000000";

Expand Down Expand Up @@ -183,7 +177,7 @@ class TimestampController {
getMerkleProof(leaf: [any]): MerkleProof {
if (!this.merkleTree) {
throw new TimestampControllerError(
"No merkle tree available. Initialize with leaves to use this method."
"No merkle tree available to generate proof"
);
}
return {
Expand All @@ -200,7 +194,7 @@ class TimestampController {
getAllMerkleProofs(): MerkleProof[] {
if (!this.merkleTree) {
throw new TimestampControllerError(
"No merkle tree available. Initialize with leaves to use this method."
"No merkle tree available to generate proofs"
);
}
return Array.from(this.merkleTree.entries()).map(([index, [value]]) => {
Expand Down Expand Up @@ -228,14 +222,10 @@ class TimestampController {
leaf: [any],
proof: string[],
leafCreationTime: Date,
maxTimeDifference: number = 30 * 24 * 3600,
maxTimeDifference: number,
leafEncoding: string[] = ["string"]
): Promise<{ verified: boolean; reason?: string }> {
if (!this.rootHash) {
throw new TimestampControllerError(
"No root hash available. Initialize with leaves or provide a root hash."
);
}
const rootHash = this.getRootHash();

const events = await this.getHintValueChangedEvents();
if (events.length === 0) {
Expand Down Expand Up @@ -268,7 +258,7 @@ class TimestampController {
}

const verified = StandardMerkleTree.verify(
this.rootHash,
rootHash,
leafEncoding,
leaf,
proof
Expand Down Expand Up @@ -297,10 +287,16 @@ class TimestampController {
* @param event - The event to get the timestamp for.
* @returns A promise that resolves to the block timestamp.
* @private
* @throws {TimestampControllerError} If no timestamp can be retrieved
*/
private async getRootHashBlockTimestamp(event: EventLog): Promise<number> {
const block = await this.provider.getBlock(event.blockNumber);
return block!.timestamp;
if (!block?.timestamp) {
throw new TimestampControllerError(
`Failed to get block timestamp for event transaction ${event.transactionHash}`
);
}
return block.timestamp;
}

/**
Expand Down
20 changes: 16 additions & 4 deletions test/integration/TimestampController.integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,13 @@ describe("TimestampController (Integration)", () => {
.then((block) => block!.timestamp);
await controller.anchorRootHash();
const proof = controller.getMerkleProof(["data1"]);
const leafCreationTime = new Date(currentBlockTime);
const maxTimeDifference = 30 * 24 * 3600;
const verified = await controller.verifyProof(
proof.leaf,
proof.proof,
new Date(currentBlockTime)
leafCreationTime,
maxTimeDifference
);

expect(proof).toBeDefined();
Expand All @@ -76,12 +79,15 @@ describe("TimestampController (Integration)", () => {
.then((block) => block!.timestamp);
await controller.anchorRootHash();
const proofs = controller.getAllMerkleProofs();
const leafCreationTime = new Date(currentBlockTime);
const maxTimeDifference = 30 * 24 * 3600;
const verified = await Promise.all(
proofs.map((proof) =>
controller.verifyProof(
proof.leaf,
proof.proof,
new Date(currentBlockTime)
leafCreationTime,
maxTimeDifference
)
)
);
Expand All @@ -94,10 +100,13 @@ describe("TimestampController (Integration)", () => {
it("should fail to verify wrong proof", async () => {
await controller.anchorRootHash();
const proof = controller.getMerkleProof(["data1"]);
const leafCreationTime = new Date();
const maxTimeDifference = 30 * 24 * 3600;
const verified = await controller.verifyProof(
["data2"],
proof.proof,
new Date()
leafCreationTime,
maxTimeDifference
);

expect(proof).toBeDefined();
Expand All @@ -106,10 +115,13 @@ describe("TimestampController (Integration)", () => {

it("should fail to verify proof for non-anchored root hash", async () => {
const proof = controller.getMerkleProof(["data1"]);
const leafCreationTime = new Date();
const maxTimeDifference = 30 * 24 * 3600;
const verified = await controller.verifyProof(
proof.leaf,
proof.proof,
new Date()
leafCreationTime,
maxTimeDifference
);

expect(proof).toBeDefined();
Expand Down
4 changes: 2 additions & 2 deletions test/integration/setup/globalSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { FORK_BLOCK_NUMBER, FORK_URL } from "./utils";

export default async function () {
return await startProxy({
port: 8545, // By default, the proxy will listen on port 8545.
host: "::", // By default, the proxy will listen on all interfaces.
port: 8545,
host: "::",
options: {
chainId: 11155111,
forkUrl: FORK_URL,
Expand Down
Loading

0 comments on commit 8d8b022

Please sign in to comment.