Watermark#
The watermark module (wmk) embeds keyed ownership evidence in a
physical design and checks it back. It implements PDMarks, a Kerckhoffs-compliant watermarking
scheme whose security rests on a secret key rather than on hidden construction:
the algorithms are public and only the key is not. Three stages carry marks:
Placement: a keyed subset of same-row, same-width cell pairs is put into a keyed left-to-right order
Clock tree: a keyed subset of leaf clock buffers (LCB) is driven to a keyed sequential-fanout parity
Routing: a keyed subset of signal nets is routed under an inflated cost for wrong-way wiring, and detected as a population effect
The key selects both which objects are marked and what value each carries, so an observer who knows the algorithm still cannot locate the marks. Verification needs no flow and no re-run: it reads a claim file, or for routing the key alone, against any loaded database.
Every stage is verified with its key. A placement or clock-tree claim file names the marked objects and nothing more: which of each pair is the target and the value it was driven to are derived again from the stage key at verification, and a claim file that records anything else is refused. Without the key a claim file is a list of names that anyone could write to match any layout; with it, the target values are the key’s and cannot be chosen after the fact. The routing stage recovers its marked set from the key alone.
What the key does not establish is when the key and the claims existed. With the key, anyone could still choose which pairs to claim after seeing a layout, so the scheme assumes that the key (or a commitment to it) and the claim files were timestamped before the design was released. That sealing is outside this module (see Limitations).
Commands#
Note
Parameters in square brackets
[-param param]are optional.Parameters without square brackets
-param2 param2are required.
Generate Watermark Key#
The generate_watermark_key command draws a secret key and derives the three stage
keys from it. It returns a dictionary with the entries key_hex, nonce_hex,
design_id, placement, cts and routing.
A stage key is HMAC-SHA256(key, design_id, nonce, "stage=" || stage).
The nonce identifies one watermark instance/run. The same secret key (key) and design
identifier (design_id) with a different nonce mark different objects with different values,
so one secret key can mark several copies of a design distinguishably, and can
mark it again after a revision without repeating the previous marks. The design
identifier and the nonce are public and belong with the design’s records; only
the secret key does not. All three are needed again to verify, so -public_file
writes the two public ones on their own, leaving -file as the only thing that
has to be kept secret.
The secret key is never logged.
When using -file, the key is written with owner-only permissions and left
out of the returned dictionary, which then holds only design_id and
nonce_hex: an interactive session echoes every command’s result, and a key
meant for a file should not also end up on the screen. The keyed commands read
the file back through -key_file. Existing private files with group or other
permissions are rejected without modification. Private and public destinations
must refer to different files, including through symbolic or hard links. Both
outputs are validated and fully written to temporary files before either
destination is replaced; each replacement is an atomic rename. If the system
random source cannot be accessed, the command fails rather than falling back to
a predictable source.
Key handling. A key given on the command line as -key_hex is visible to anything that
records command lines: the interactive history file (~/.openroad_history),
shell history when the command is passed through a shell, and process listings
while the command runs. Prefer -file at generation and -key_file on every
keyed command, and keep keyed commands in scripts rather than typing them at
the prompt. A keyed command takes the stage key stored in the file, or derives
it from key_hex, design_id and nonce_hex when only those are present.
Generated files begin with # OpenROAD watermark key file v1, followed by a
Tcl list of alternating field names and values. Values use Tcl list quoting,
so empty values, whitespace and multiline design identifiers are preserved.
The files use UTF-8 encoding and are parsed as data; their contents are never
executed. Legacy files without the header remain readable as one unquoted
name value pair per line. Unknown or duplicate fields and invalid keys are
rejected, and diagnostics omit the file contents to avoid exposing secrets.
generate_watermark_key
-design_id design_id
[-file file]
[-key_hex key_hex]
[-nonce_hex nonce_hex]
[-public_file public_file]
Options#
Switch Name |
Description |
|---|---|
|
Identifier of the design version being marked (e.g., |
|
Write the secret key, nonce and stage keys to this path, readable only by its owner, and return only the public parameters. |
|
Use this 64-character hex secret key instead of drawing one. |
|
Use the specified nonce instead of generating a random nonce. The nonce must be provided as an even-length hexadecimal string. Automatically generated nonces are 16 bytes (128 bits). |
|
Write the design identifier and the nonce to this path. These are the public inputs to key derivation and carry no key material, so this file can be kept with the design’s records and passed on. |
Derive Watermark Key#
The derive_watermark_key command re-derives one stage key and returns it as a
64-character hex string. Verification runs in a later process than embedding, so
this recovers the keys that were used without any of them having been stored.
The inputs are given explicitly or read from a key file.
derive_watermark_key
-stage stage
[-design_id design_id]
[-key_file key_file]
[-key_hex key_hex]
[-nonce_hex nonce_hex]
Options#
Switch Name |
Description |
|---|---|
|
|
|
The identifier the keys were generated with. |
|
A key file written by |
|
The 64-character hex secret key. |
|
The nonce the keys were generated with. |
Place Watermark#
The place_watermark command puts a keyed subset of cell pairs into a keyed
left-to-right order and writes the claimed pairs to -claims_file. It returns
the number of pairs claimed. Run it after detailed placement.
Only cells in the same row with the same width are paired, so a swap leaves the
row legal and the area unchanged. Candidates are screened on slack and on the
wirelength a swap would cost; the key then orders what survives and takes a
greedy non-overlapping prefix. The design is re-legalized afterwards, and a pair
whose cells then lost more than -guard_degrade_ns of slack is put back.
After any rollback and legalization, parasitics are re-estimated and the final
placement is checked against the original constrained endpoint setup and hold
slacks in every analysis scene. If selective rollback cannot meet the budget,
the entire original placement is restored, nothing is claimed, a header-only
claim file is written and the command returns 0: a design that carries none
of the mark has no evidence to publish. A requested guard without usable
constraints and signal wire RC produces a warning; Liberty alone does not
activate it. With timing available, the slack screen excludes unconstrained
candidate cells, and a marked cell that had no constrained slack is judged by
the design-wide endpoint check alone.
Only ordinary standard cells (LEF class CORE) that are placed, not fixed and
not marked dont-touch are eligible. Fillers, taps, endcaps, antenna cells and
tie cells are not: the flow may delete or re-create them, and a claim on such a
cell is evidence that evaporates.
Every pair chosen is claimed, including pairs restored by the timing guard and any whose mark did not survive legalization. Removing failed pairs after observing the design would artificially inflate the extraction rate, in which case it would be 1.0 on every design.
place_watermark
-claims_file file
[-grid_nx n]
[-grid_ny n]
[-guard_degrade_ns ns]
[-hpwl_eps_um eps]
[-key_file key_file]
[-key_hex key_hex]
[-max_disp_um disp]
[-min_pairs_total n]
[-pair_dist_um dist]
[-pairs_per_tile n]
[-slack_threshold_ns slack]
Options#
Switch Name |
Description |
|---|---|
|
Where to write the claims. |
|
Key file holding the placement key. One of |
|
64-character hex placement key. |
|
Tiles across and down the core, so marks are spread rather than clustered. Both default to |
|
Slack a pair may cost before it is put back. Defaults to |
|
Largest half-perimeter wirelength change a swap may cost, in microns. Defaults to |
|
Displacement allowed during re-legalization, in microns. Defaults to |
|
Below this many pairs, search again with a wider neighbourhood and twice the wirelength budget. Defaults to |
|
How far along the row to look for a partner, in microns. Defaults to |
|
Most pairs to claim per tile. Defaults to |
|
Cells with less slack are left alone. Defaults to |
CTS Watermark#
The cts_watermark command drives a keyed subset of leaf clock buffers (LCBs) to a
keyed sequential-fanout parity and writes the claimed pairs to -claims_file.
It returns the number of pairs claimed. Run it after clock tree synthesis and
before routing, on a flat design linked without -hier. Hierarchical CTS
embedding is rejected before any edits because moving a sink also requires
updating module ports and nets, and a design whose clock nets already carry
wires or guides is rejected because reconnecting a pin would leave that
geometry describing the old connectivity. CTS verification remains available
for hierarchical and routed designs.
LCBs are marked in pairs. Parity is changed by moving one flip-flop’s clock
pin from one LCB of the pair to the other; a buffer only lends a sink when it
drives at least two, so no buffer is left driving nothing. A move is undone if
it increases clock latency spread by more than -skew_margin_ns, degrades a
constrained endpoint’s setup/hold slack by more than -slack_margin_ns,
violates either LCB’s max-fanout limit, or leaves either LCB with insufficient
slew or capacitance headroom. Protected source/destination nets and fixed or
protected sink instances are not modified. A connection, extraction or timing
error restores the trial sink’s original net and refreshes both nets’
parasitics before propagating the error; if any part of that rollback fails,
the command reports the modified connectivity as its own error.
Pairs must have identical, nonempty clock sets in the active timing modes, including mode identity, and provably equivalent clock logic. The command traces only unconditional Liberty buffers and inverters, requires a common source net and matching inversion parity, and stops at gates, muxes and sequential cells. Case analysis does not remove these boundaries. Floating nets, multiple drivers and cycles are rejected. Leaf carriers must be Liberty buffers or inverters; STA identifies sequential clock pins without relying on LEF flags or pin names.
The skew guard measures the latest minus earliest propagated clock latency at
sequential clock pins, separately for each clock, analysis scene and source
edge. It compares against the original design throughout embedding; the budget
does not reset after each accepted move. This conservative latency spread is
different from the path-based report_clock_skew metric, which considers data
path relationships, uncertainty and common-path pessimism removal. A separate
STA guard checks every originally constrained endpoint’s setup and hold slack,
for each scene and transition, against its fixed baseline with the
-slack_margin_ns budget. This catches path degradation even when the overall
latency spread is unchanged.
Read Liberty and constraints, set signal and clock wire RC, estimate parasitics, and propagate clocks before embedding. A pair without measurable clock timing is left unchanged and the command reports the unavailable guard. It remains a claim if selected.
cts_watermark
-claims_file file
[-cap_headroom_frac frac]
[-key_file key_file]
[-key_hex key_hex]
[-num_pairs n]
[-sibling_dist_um dist]
[-skew_margin_ns margin]
[-slack_margin_ns margin]
[-slew_headroom_frac frac]
Options#
Switch Name |
Description |
|---|---|
|
Where to write the claims. |
|
Fraction of the effective capacitance limit left unused. Defaults to |
|
Key file holding the clock-tree key. One of |
|
64-character hex clock-tree key. |
|
Most pairs to mark, one bit each. Defaults to |
|
Largest distance between the two buffers of a pair, in microns. Defaults to |
|
Maximum clock latency-spread increase, in ns. Defaults to |
|
Maximum setup or hold slack degradation at a constrained endpoint, in ns. Defaults to |
|
Fraction of the effective slew limit left unused. Defaults to |
The slew reserve is checked for each scene and rise/fall edge; capacitance
reserve is checked in each scene. Limits include applicable SDC constraints and
Liberty limits. Both headroom fractions must be finite and in [0, 1].
Max fanout is checked in every timing mode using STA’s effective SDC/Liberty
limit and Liberty fanout loads. A load equal to the limit is allowed; no
additional fractional fanout reserve is required. A rejected move remains in
the claims denominator, so fanout limits can reduce the attainable match rate.
Placement and CTS distances and timing budgets must be finite and nonnegative.
Distances must fit in 2147483647 database units; timing budgets must not
exceed one second. Grid dimensions must be positive integers;
pair counts must be nonnegative integers. Zero requested pairs produces no
marks. Invalid options are rejected before changing the design or claims file,
including through the Python API.
Placement and CTS claims are staged in the output directory and replace the requested file only after writing and closing succeed. The parent directory must exist and be writable; the destination must be a regular file or a new path. Devices, directories and output symlinks are rejected. If publication fails, the command reports an error, preserves any previous claims file, and restores its placement or clock-connection edits with refreshed parasitics. Successful embedding with no selected pairs writes a header-only claims file, replacing any previous claims. Such a file provides no ownership evidence.
Set Routing Watermark#
The set_routing_watermark command selects a keyed subset of signal nets, tags
each one, and returns the number tagged. A net is selected when the first four
bytes of HMAC-SHA256(key, "net\0" || name), read as a little-endian integer
over 2^32, fall below the fraction. Previous tags are cleared first, so repeated
calls are idempotent. Call it before detailed_route.
set_routing_watermark
[-fraction fraction]
[-key_file key_file]
[-key_hex key_hex]
Options#
Switch Name |
Description |
|---|---|
|
Finite fraction in |
|
Key file holding the routing key. One of |
|
64-character hex routing key. |
The cost the detailed router applies to wrong-way wiring on tagged nets is
router configuration: see set_routing_watermark_strength and
get_routing_watermark_strength in the detailed router
documentation. The default strength is 100; 1 tags nets without biasing
them, which is the control case.
Report Routing Watermark#
The report_routing_watermark command ranks every routed signal net by its
wrong-way wirelength fraction and reports how many tagged nets fall below the
cutoff, with the resulting coincidence probability. Run it after
detailed_route.
report_routing_watermark
[-p p]
Options#
Switch Name |
Description |
|---|---|
|
Finite quantile in |
Clear Routing Watermark#
The clear_routing_watermark command removes every watermark tag from the
current block and returns the number cleared.
clear_routing_watermark
Verify Watermark#
The verify_watermark command checks the loaded design and returns 1 when it
carries the watermark. Ownership is granted when at least -min_stages of the
checked stages pass. Every stage checked needs its stage key, given as
-placement_key_hex, -cts_key_hex and -routing_key_hex or read from a key
file with -key_file.
Placement and clock-tree marks are read from their claim files. For each
claim the verifier derives the target from the key – the keyed bit of a
placement pair, or the target buffer and parity of a clock-tree pair – and
refuses the file with an error if any claim recorded something else, or if two
clock-tree claims derive the same target buffer. A file the key does not
reproduce was not written by an embedder holding the key, and scoring it would
score claims nobody committed to. The claims that remain are judged by the
extraction rate against -tau and a count-dependent evidence threshold
-claim_alpha. With n distinct checkable claims and h matches, the latter
uses the inclusive binomial tail P[Binomial(n, 0.5) >= h]. A stage counts as
passing only when its rate is at least -tau and this probability is at most
-claim_alpha (default 1e-4). A nonempty but insufficient sample is reported
as insufficient evidence and does not contribute a passing stage.
For example, one perfect bit has probability 0.5; one matching placement bit
and one matching CTS bit cannot produce a default ownership verdict. Thirteen
perfect matches have probability 1/8192, which fails the default threshold;
fourteen have probability 1/16384, which passes. At 48 matches out of 64,
the rate is 0.75 and the probability is approximately 3.87e-5, satisfying
both defaults. -min_stages 1 changes the required number of passing stages;
it does not waive their evidence requirements. Empty claim sets are skipped.
This calculation assumes distinct, precommitted, independent fair target bits
under the unmarked-design model. It is not a probability that an ownership
assertion is true, an authentication check on the claims, or a measured
false-positive rate on real designs. Thresholds are per stage; stage voting
does not establish independence between stages. Repeated keys, parameters or
claim subsets selected after observing a suspect design require a separate
multiple-testing analysis. Choose thresholds before inspecting the result.
The C++/Python VerifyResult retains checked, held, rate() and pValue();
extraction can be inspected even when a design has insufficient capacity.
CTS verification requires Liberty to identify buffers and sequential clock pins using the same definitions as embedding. Missing Liberty is an error; a claimed carrier with unknown Liberty fanout remains in the denominator and fails its claim. Timing constraints are not required just to observe the fanout parity.
Routing has no claim file. Its marked set is recovered from -routing_key_hex
alone and judged by how improbable the marked nets’ wrong-way wirelength is under
a random choice of marked set, against -routing_alpha. The sign of the
difference is not evidence on its own, since it is a coin flip on an unmarked
design.
verify_watermark
[-claim_alpha alpha]
[-cts_claims file]
[-cts_key_hex key_hex]
[-key_file key_file]
[-min_stages n]
[-placement_claims file]
[-placement_key_hex key_hex]
[-routing]
[-routing_alpha alpha]
[-routing_fraction fraction]
[-routing_key_hex key_hex]
[-routing_permutations n]
[-tau tau]
Options#
Switch Name |
Description |
|---|---|
|
Largest binomial chance probability for a placement or CTS stage. Defaults to |
|
Claim file from the clock-tree watermark. Checks the clock-tree stage. |
|
64-character hex clock-tree key. |
|
Key file written by |
|
Stages that must pass. Defaults to |
|
Claim file from the placement watermark. Checks the placement stage. |
|
64-character hex placement key. |
|
Check the routing stage with the routing key from |
|
Largest p-value the routing stage may show and still pass. Defaults to |
|
The fraction the routing mark was embedded with. Defaults to |
|
64-character hex routing key. Checks the routing stage. |
|
Draws behind the raw sampled p-value, whose minimum is 1/(n+1) before the two-test adjustment. Defaults to |
|
Extraction rate a placement or clock-tree stage must reach. Defaults to |
At least one of -placement_claims, -cts_claims, -routing_key_hex or
-routing is required.
The routing stage also reports a closed-form bound on the same tail, exact when
the marked nets carry no wrong-way metal. That is the case a working watermark
produces, and it reaches probabilities sampling cannot express: on a routed jpeg
the sampled p-value floors at 1e-5 while the closed form gives 1e-515. The
decision uses min(1, 2 * min(sampled_p, closed_form_bound)). This Bonferroni
adjustment allocates half of -routing_alpha to each test and does not require
them to be independent. Taking the unadjusted minimum can exceed the configured
false-positive rate, because the sampled p-value is not an upper bound on the
exact tail. Both raw components remain in the report; C++ and Python callers can
use RoutingStat::pValue() to obtain the adjusted value used by Tcl.
The sampler compares marked subset sums directly. It counts ties, including differences within a rounding allowance that scales with the number and magnitude of the summed fractions. This can increase the reported p-value conservatively; roundoff must not turn equal routing fractions into ownership evidence. Sorting the fractions makes the result independent of net iteration order.
Claim file format#
A claim file records which objects an embedder marked. It is comma-separated
with a header row, and columns are matched by name, so a producer may emit them
in any order and add columns of its own. Missing required columns, empty or
duplicate header names, malformed row widths, and invalid fields in checkable
claims are errors; verification never scores a partially parsed file. The error
identifies the file and line. skipped_reason is required even when every
value is empty. A checkable claim must name two different instances, and a
repeated unordered pair (including reversed names) is an error regardless of
target bit or other metadata. Skipped candidate records do not contribute
evidence or consume a scored carrier. Values are not quoted. Names must be
nonempty, contain no commas, line breaks or NULs, and have no leading or
trailing spaces or tabs. Both embedders validate all eligible instance names
before selecting marks or changing the design and fail explicitly if the format
cannot represent a name. A line longer than 65536 bytes, more than 64 columns
or more than 1048576 rows is refused before anything is read into memory.
The target value recorded in a claim is the owner’s record of what the key
called for, and verification checks it against the key rather than trusting
it: the placement bit is HMAC-SHA256(key, "bit", first, second) for the two
names in lexicographic order, and the clock-tree target and parity come from
HMAC-SHA256(key, "pair", first + "+" + second, first, second).
Each row represents either a watermark claim or a candidate that was skipped.
A row is verified when skipped_reason is empty. The special value
already_satisfied is also treated as a valid claim: the object already matched
the target value and therefore required no modification.
Any other non-empty skipped_reason indicates that the candidate was not
included in the final claim set and is ignored during verification.
Once an object has been selected as a watermark claim, it remains a claim even
if embedding fails to achieve or preserve its target value. Such a failed claim
must still be recorded and will count against the extraction rate. This prevents
failed claims from being removed after observing the final design, which would
artificially inflate the extraction rate.
Placement claims describe pairs of cells in the same row. The bit is which of the two sits further left, comparing instance bounding boxes.
Column |
Meaning |
|---|---|
|
|
|
Instance names of the marked pair. |
|
|
|
Empty or |
kind,id,A_name,B_name,target_bit,skipped_reason
pair,_101770_|_101842_,_101770_,_101842_,0,already_satisfied
pair,_070816_|_070839_,_070816_,_070839_,1,
The embedder writes the two names in lexicographic order, which is the order the key derives the bit for; a producer that writes them the other way round records the bit relative to its own order, and the verifier accounts for that.
CTS claims describe pairs of leaf clock buffers. The bit is the parity of the target buffer’s sequential fanout.
Column |
Meaning |
|---|---|
|
Instance name of the marked leaf clock buffer. Must be the buffer the key selects from the pair. |
|
The other buffer of the pair. |
|
Parity the key called for, |
|
The pair’s identifier, |
|
Parity the embedder achieved. Recorded for the owner; not read by the verifier. |
|
As above. |
pair_idx,pair_key,target_lcb,other_lcb,target_bit,final_bit,skipped_reason
0,clkbuf_leaf_313_clk+clkbuf_leaf_314_clk,clkbuf_leaf_314_clk,clkbuf_leaf_313_clk,1,1,
A claim naming an instance that is not in the design counts against the extraction rate rather than aborting the check.
Example scripts#
Generate the keys once, keeping the secret in a file:
generate_watermark_key -design_id jpeg_NG45_v1 -file wm.key \
-public_file wm.public
Mark all three stages:
detailed_placement
place_watermark -key_file wm.key -claims_file wm_place.csv
clock_tree_synthesis -buf_list $buffers -root_buf $root_buf
set_propagated_clock [all_clocks]
estimate_parasitics -placement
cts_watermark -key_file wm.key -claims_file wm_cts.csv
set_routing_watermark -key_file wm.key -fraction 0.02
set_routing_watermark_strength 100
global_route
detailed_route
Check a suspect layout, which need not be one this process built:
read_db suspect.odb
verify_watermark -key_file wm.key \
-placement_claims wm_place.csv \
-cts_claims wm_cts.csv \
-routing -routing_fraction 0.02
Regression tests#
There are a set of regression tests in ./test. For more information, refer to
this section.
Simply run the following script:
./test/regression
Limitations#
A technology whose router never wires against the preferred direction has no routing watermarking. The stage reports this and is skipped, and ownership rests on placement and the clock tree. ASAP7 is such a technology.
Watermarking in distributed detailed routing has not been validated. The watermark property is stored in the database, but this module’s regressions exercise only local detailed routing.
Capacity is a property of the design. A sparse or timing-tight design may yield few placement pairs or none, and a shallow clock tree too few leaf buffers to pair.
Certificate sealing and the timestamped key commitment are not part of this module. Verification proves that whoever holds the key also holds claims the key derives; it cannot tell claims chosen before a design was released from claims a key holder picked to fit it afterwards. That the key and the claim files existed before release is what sealing them establishes, and its guarantee comes from an independent timestamping authority rather than from anything the tool can check.
Using the Python interface to wmk#
The same work can be done from openroad -python through the Watermark object
on the design:
from openroad import Tech, Design
import wmk
watermark = design.getWatermark()
options = wmk.PlacementOptions()
watermark.placementWatermark(key_hex, options, "wm_place.csv")
watermark.selectNetsKeyed(key_hex, 0.02)
result = watermark.verifyPlacement(key_hex, "wm_place.csv")
A key is passed as the 64-character hex string or as 32 raw bytes. Anything else
is refused. Key generation has no Python entry point; use
design.evalTclString("generate_watermark_key -design_id ...").
References#
A. B. Kahng and Y. Liu. Kerckhoffs-Compliant Watermarking for Physical Design IP Protection: From Placement to Routing. arXiv preprint arXiv:2608.05055. (arXiv)
License#
BSD 3-Clause License. See LICENSE file.