Skip to main content

Smart Contract

The Veylent smart contract is the on-chain component of Veylent. It records which block each anchor ID was anchored in, and emits events that serve as the immutable proof of signing.

Contract Interface​

event DocumentAnchored(
bytes16 indexed anchorId,
bytes32 indexed compositeHash,
address indexed signer,
bytes16 previousAnchorId,
uint256 timestamp
);

address public immutable trustedRelayer;

mapping(bytes16 => uint256) public anchorBlock;

error UnauthorizedRelayer(address caller);
error InvalidAnchorId();
error AlreadyAnchored(bytes16 anchorId);
error UnknownPreviousAnchor(bytes16 previousAnchorId);

function anchorDocument(
bytes16 anchorId,
bytes32 compositeHash,
bytes16 previousAnchorId
) external onlyRelayer;

anchorDocument Function​

Records a document signature on-chain. Only callable by the trusted relayer address set at deployment.

Parameters:

  • anchorId -- Random 16-byte ID chosen by the signing app and printed in the signature's QR code
  • compositeHash -- SHA-256 of the signing record JSON (includes anchor ID, document hash, signer info, timestamp, salt)
  • previousAnchorId -- Anchor ID of the previous signature on the same document (zero for the first signature)

Behavior:

  1. Reverts with UnauthorizedRelayer if the caller is not the trusted relayer
  2. Reverts with InvalidAnchorId if anchorId is zero
  3. Reverts with AlreadyAnchored if anchorId was anchored before
  4. Reverts with UnknownPreviousAnchor if previousAnchorId is non-zero and was never anchored
  5. Stores anchorBlock[anchorId] = block.number
  6. Emits a DocumentAnchored event with the provided data, _msgSender(), and block.timestamp

Because an ID can be anchored only once and a previous anchor must already exist, a signing history can only be extended. It cannot be forked onto made-up predecessors.

anchorBlock Mapping​

anchorBlock(anchorId) returns the block number the anchor ID was anchored in, or zero if never. Anyone can use it to find an anchor's event with a single-block log query, without relying on the Veylent server's database.

Access Control​

The contract restricts who can anchor documents via an onlyRelayer modifier. The trustedRelayer address is set as an immutable in the constructor, meaning it cannot be changed after deployment. This prevents unauthorized parties from polluting the contract with fake anchors.

If the relayer key needs to be rotated, a new contract must be deployed. Old signatures stay verifiable on the old contract.

DocumentAnchored Event​

The event serves as the permanent record. Event fields:

FieldTypeIndexedPurpose
anchorIdbytes16YesIdentifies the signature (as printed in its QR code)
compositeHashbytes32YesCommits to the signing record
signeraddressYesBlockchain address that submitted the tx (the relayer)
previousAnchorIdbytes16NoLinks to previous signature (chain)
timestampuint256NoBlock timestamp at anchoring

Signature Chaining​

When a document is signed multiple times, each signature references the previous one by anchor ID:

Signature 1                   Signature 2                   Signature 3
anchorId = A1 anchorId = A2 anchorId = A3
compositeHash = H1 compositeHash = H2 compositeHash = H3
previousAnchorId = 0x000...0 previousAnchorId = A1 previousAnchorId = A2

The verification API walks this chain backwards, from the scanned signature to the first, to present the complete signing history.

Development Setup​

The project uses Hardhat for local blockchain development:

# Start local blockchain
npx hardhat node

# Deploy contract
npx hardhat run scripts/deploy.ts --network localhost

Gas Costs​

Each anchor writes one storage slot (anchorBlock[anchorId]) and emits one event. The event data lives in transaction logs, not contract storage. The storage slot is what lets anyone find the event without an index.

Verification Security​

The verification API enforces two checks to ensure anchors are authentic:

  1. Contract address pinning -- The API reads anchorBlock and queries events only on the official VEYLENT_CONTRACT_ADDRESS. Events from copycat deployments are never seen.
  2. Relayer restriction -- The onlyRelayer modifier on-chain ensures only the trusted relayer can write to the contract, preventing unauthorized anchors at the source.

Together, these prevent both copycat contracts and unauthorized writes to the official contract.

Upgradeability​

The current contract is not upgradeable. Once deployed, the contract code is immutable. This is intentional -- it means no one can change the rules after deployment, which strengthens the trust model.

If a new version is needed, a new contract is deployed and the API is updated to point to it. Old signatures remain verifiable on the old contract.