IEEE 1735 IP Protection

Compile encrypted SystemVerilog IP, and encrypt your own IP for RyuSim with ryusim-protect.

Overview

IEEE 1735 defines how an IP vendor ships HDL source encrypted, so that only the tools the vendor chose can read it. The source carries `pragma protect envelopes: the design text encrypted with a random AES session key, and that session key encrypted (wrapped) with each tool's RSA public key.

RyuSim does not ship the RSA private key that unwraps its session keys. Seiraiyu's key broker holds it. When a source file contains an envelope, ryusim compile sends the wrapped session key to the broker over HTTPS, gets the session key back, decrypts the design in memory and compiles it. Keeping the private key off the user's machine answers the key-extraction attacks that Speith et al. showed against local key storage (IEEE S&P 2022).

Every compile of protected source needs network access to the broker. There is no offline mode.

Compile protected IP

Pass the encrypted file like any other source. No extra option is needed:

ryusim compile --top pipelined_adder pipelined_adder.svp

RyuSim checks every source file for protected regions, whatever its extension. .svp is a convention. The broker URL defaults to https://broker.ryusim.com/api/v1; --broker-url changes it, and ryusim -v compile prints the one in use. If the RYUSIM_API_KEY environment variable is set, RyuSim sends it to the broker as a bearer token. The public broker does not require one.

In a cocotb flow, list the .svp file in VERILOG_SOURCES. The cocotb guide covers the rest.

Try it: an encrypted pipelined adder

This file is encrypted for RyuSim. The ports and the parameter are plaintext; the body is not. Save it as pipelined_adder.svp:

module pipelined_adder #( parameter WIDTH = 8 ) ( input logic clk, input logic rst, input logic [WIDTH-1:0] a, input logic [WIDTH-1:0] b, output logic [WIDTH:0] sum, output logic valid ); `pragma protect version = 2 `pragma protect encrypt_agent = "ryusim-protect" `pragma protect key_keyowner = "Seiraiyu" `pragma protect key_keyname = "RyuSim-RSA-2026-01" `pragma protect key_method = rsa `pragma protect key_block KUNB27Vcm4Xhq16d59nvsQGT9HzJjaRgve+imwNX9AlD2TVngWh3FIVi3XyIjf5DV7OU1vgT09gcxODE7Kf4fBH02x8TULG7rSJGpW9tjmx+6SPceQVVaa4hT7w9bwN2v5BGHPgeTerZuVs6H4g/I/kl6CHkQAJgEIFU51N6ucMqiYbcPhyc15VybXq1iBNI6QKFNw+QtA4EOQMfnww33g8qBO826U3sq1FYQ7vNCsUHgncMyZERKkqYuYUplwYfq50DlzTNnltbhZSM9du2xNOlicmDcKgMvjeyVPbrUrRQ8k5Gl0+9j6cj8MOtL6wMtBTVX0nTt7wb61eTuB2YVFKfODpQXRyu2ZPuNP8lrAPfyzE33ElLsxq6sZh2psweTmhbXMKrkYD3quuaImbR2L3GbWiH/4Mo/t0VLC3kb/zh+gB/K42jJRfA5ljqK9XqI9fyOWIYyBk0iU4UftiObE27sWbWy17Xm8BtqQXqpEdOrhbkZYJS1+dVgi3XHgmlRY18+nykBZWAlfzif85AQnNcRzzDvf3y5bItxUx9EF3iXGsQ1qrAEUN+iRY0dWC7+Hf8gY/qqTSjtzbyDlpS8CweitBSG3Q4IqBrIqyHjmNHRo4p7ehb8WG9pYkEP9zyQCf9/GLXZrZ4BRJ+8iKP9bc74H0C5utAZbg3caNUjnA= `pragma protect data_method = "aes-256-gcm" `pragma protect iv = "6cdf01d6adb98217219aa68f" `pragma protect begin_protected `pragma protect data_block iFJYVYfclfGcGOgXdun96UA+IerbCZf0YgSyfGCvPie6shRDy/TtJ7ggMgB60XIiD9Tr0XUMip+D4pVtbhGkq34u3PjKaXX4IuJ7wiyyN8zrBiDLQd65pqYTeiw3ySFRXpjlPIEcY1szNCdwqyKOSGxFwG9KPwFKD8P6wjlK0cQwlYpcE1I/eNarUUd4AO0CoucZsyQLdK6RO6xaRZGNSiCXIdtTniato5B7KHV59RXAW7Rr0EyJLMxIKnd98GpiAXya8HXsREhSQKuX2YlLPb2Pxha+JvWEVZWvOT6kQJJkYA6rbApkNxJOP8QYHSVv+388SKc6lZLsoYbTJtDDndw1OA5cF4wZZzSIS4E4JF4OxKscVsvQKjkS5MIqPBSe0abTLKX8nVTNRmD/DGbt3h4ox/dGYlJ6Ye7SqSxXqq8AE6IU8Df8rBhqPA06mDMA3ErSh7n/75nspnP9pKm/RzyAS0r/aoNM57sgxYFvVgUsTKWfVjZc2iiPyI5sKkICyyazdaKN+9B99SPnh03GhN2Bs9y1hQXBKU8MIcHKd5JRTfOMSC4YTPpg37rhg3/4YDCWvwb7ohRmulTWROB/hHA691thNoQHtqgNlQaPc9qaUAlqZl803OPRCsGD2pZ2KgFEX7ScYDqd3qXGheRROyDfVmaROpea5kjJunGqLKNhmDpsh0nrHOvZD0tnjQt6ozct1B3zrioqR3AkrkPKtp53sGIpBZVEOR0/H8nWfo9jT2WTH2RXCXrpFQ2dmv4RdcI7AgxbrJMTT775g2UqP+18WUMNPtmTI36yXBJ5IWwCh0RlXo7c8zIFlI3xyiNVaKnAXjnY5FkQv03XbtYStn/JRej3W20xBSh5pZhNOzD5Q+YI0Qmia6lsqUavriu+FR+jogCc/9KutYDJloMGF1/H1K/SKg8S `pragma protect auth_tag = "bf5f6dc6a5edcd53cd5273d581df6ebc" `pragma protect end_protected endmodule

Save a cocotb test as test_pipelined_adder.py:

import cocotb from cocotb.triggers import RisingEdge, Timer from cocotb.clock import Clock @cocotb.test() async def test_pipelined_add(dut): """Verify the encrypted pipelined adder compiles and simulates.""" cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start()) # Reset dut.rst.value = 1 dut.a.value = 0 dut.b.value = 0 for _ in range(3): await RisingEdge(dut.clk) dut.rst.value = 0 # Apply inputs and wait for pipeline to produce results dut.a.value = 10 dut.b.value = 20 for _ in range(5): await RisingEdge(dut.clk) await Timer(1, unit="ns") assert dut.valid.value == 1, "valid should be asserted after pipeline fill" result = int(dut.sum.value) assert result == 30, f"Expected 10+20=30, got {result}" dut._log.info(f"Encrypted IP works! sum={result}")

and a Makefile:

SIM ?= ryusim TOPLEVEL_LANG := verilog VERILOG_SOURCES = $(PWD)/pipelined_adder.svp COCOTB_TOPLEVEL = pipelined_adder COCOTB_TEST_MODULES = test_pipelined_adder include $(shell cocotbext-ryusim-config --makefiles)/Makefile.sim

Run make. The test passes:

71.00ns INFO cocotb.pipelined_adder Encrypted IP works! sum=30 ... ** TESTS=1 PASS=1 FAIL=0 SKIP=0 71.00 0.00 84278.49 **

The blocks in this section are not run in the site's CI because they reach the broker over the network. The output above is from a run against the 2.1.16 build.

What happens during the compile

  1. Parse the envelopes. Before the SystemVerilog parser sees the file, RyuSim reads each begin_protected … end_protected region: the key blocks with their key_keyowner and key_keyname, the data_method, the iv, the data_block and the auth_tag.
  2. Check locally. RyuSim picks the key block whose key_keyowner is Seiraiyu and checks that the data_method is one it can decrypt. Both checks fail without any network request.
  3. Unwrap the session key. RyuSim sends the key block to POST <broker-url>/unwrap (by default https://broker.ryusim.com/api/v1/unwrap). For an https:// URL, TLS certificate and host verification are on. RyuSim also accepts an http:// broker URL without a warning. Over plain HTTP, the key block, the RYUSIM_API_KEY bearer token and the returned session key cross the network unencrypted. Use only https:// broker URLs. The broker decrypts it with the RSA private key and returns the AES session key. Each attempt has a 10-second connect timeout and a 30-second total timeout. Connection errors and HTTP 5xx answers are retried, up to 3 attempts with a 2 s and then a 4 s pause. HTTP 4xx answers are not retried.
  4. Decrypt. The data block is decrypted in memory. For AES-256-GCM the authentication tag is checked, and a mismatch stops the compile.
  5. Parse. The decrypted text replaces the envelope and goes to the parser as an in-memory source. RyuSim zeroes the session key and its working copies of the decrypted text with OPENSSL_cleanse. The parser keeps its own copy of the source in memory until the compile process exits.
  6. Generate and build. Modules defined in a decrypted file are marked protected. Their generated C++ files are written to obj_dir, compiled, and deleted when the build succeeds (ryusim -v compile prints Removed protected source: <file> for each). The object files stay in obj_dir for linking. The module's generated header also stays; it names the module's ports and internal signals.

Data methods

data_methodStatus
aes-256-gcm (also aes256-gcm)Supported. 256-bit key, 96-bit IV, 128-bit authentication tag. A modified payload or tag fails the compile. This is what ryusim-protect writes.
aes-128-cbc (also aes128-cbc)Supported, with a warning: warning: <file> uses AES-128-CBC without integrity protection. CBC has no authentication tag, so tampering is not detected.
anything elseRejected before the broker is contacted.

Encrypting your IP

ryusim-protect encrypts SystemVerilog for IEEE 1735 distribution. It is in the bin/ directory of the release archive, next to ryusim, and the installer puts it in the same place as ryusim. Its commands are encrypt, validate, info, list-recipients and keygen. The examples below ran with ryusim-protect 2.1.16. The site's CI does not run them, because its build tree has no ryusim-protect.

Mark what to encrypt

Put `pragma protect begin and `pragma protect end (IEEE 1800-2023 clause 34.5) around the part to encrypt. Everything outside the markers stays plaintext: the module header, ports, parameters, typedefs and defines. Users can then see the interface and override parameters. Save this as my_ip.sv:

module my_ip #( parameter int WIDTH = 8 ) ( input logic clk, input logic [WIDTH-1:0] data_in, output logic [WIDTH-1:0] data_out ); `pragma protect begin always_ff @(posedge clk) data_out <= data_in + 1'b1; `pragma protect end endmodule

Encrypt

ryusim-protect encrypt --input my_ip.sv --output my_ip.svp --recipient ryusim
Encrypted my_ip.sv -> my_ip.svp (1 recipient(s))

The result keeps the plaintext header and replaces the marked region with the envelope. The session key, IV and ciphertext are random, so every run gives a different file. This is one such my_ip.svp; the error examples further down use it:

module my_ip #( parameter int WIDTH = 8 ) ( input logic clk, input logic [WIDTH-1:0] data_in, output logic [WIDTH-1:0] data_out ); `pragma protect version = 2 `pragma protect encrypt_agent = "ryusim-protect" `pragma protect key_keyowner = "Seiraiyu" `pragma protect key_keyname = "RyuSim-RSA-2026-01" `pragma protect key_method = rsa `pragma protect key_block FPGM0net9ZMVSNjeS7ViKSf+oVfsF3Svf43W9LsNrqj/m9ctrp57ZHgudpqwFKEnHUQwK9OUdaO/1WjkrAMtA02KR+Lq93C4ERpydtqVemPFwtv2Luc/nmayQd8NYRK0aF6KSTDayJXCS+nUpBjCsU+nCoTAUseZnQnm5CJP+YlF5vEKbxkMhlzzCX0zKLWpi8m482DRpQsffGBbTcFVhrPgrLS+/K7zF+GItazmV1FFUndg+aaq7aXxw5aKLVweiGREWVctdA87RLkbVcokkM/+cQOrwCWn6ZVzg5CqBn2LzKW6VrEkb9eixFdOjF2eNWQJFG6/cbAu8cZfv064Ppy8aieFYlejjTpmY9PLB6ousXfuzmvId2YVwtqDkqLC32EC/0UvRdWdZks8blmgzkiNB52bx/OKIXc4dTLKMH3rPGWKht/1v6DU6FZ2d09GYVvcsu5QoB4jB2rhApTY57CR2kkKywo56IkAhv6sjhme+2nlWqtyMzEEKmbGOoVxlHC/BkZlHb7P28zvPwwyD3u7K+TcNIc/0G4Sypydneq4uKD6/p/zH0KRxNPglHRqhP5y+aRWrZ3yAKSJuW/1a/0cZViAc7FHKbIE3cQ+/TfXmlnS84bekCpZ2zMJlTtqhoQ9iJ52KYmiF8wiTLwxULH3MlhRDdH0DcAum0Mx72o= `pragma protect data_method = "aes-256-gcm" `pragma protect iv = "6edaf810bbba88ae4b301dfc" `pragma protect begin_protected `pragma protect data_block aGiCkSoF2UY7SO7Zmzqw9Q/6aT0R8aHNINMO2isieqpas2JPZa7SsSBAY/MEgN/J7mYf85irgwkZyKQTILdZAfQ= `pragma protect auth_tag = "416268653c3a42231c7be1ce394a5758" `pragma protect end_protected endmodule

ryusim-protect always encrypts with AES-256-GCM and wraps the session key with RSA-OAEP (SHA-256). In 2.1.16 its --data-method option changes only the data_method label written into the envelope, not the cipher, so a file written with --data-method aes-128-cbc does not compile. Leave the option at its default (aes-256-gcm).

Recipients

--recipient names a tool by its registry ID. Give it more than once to encrypt one file for several tools. Each recipient gets its own key block holding the same session key, wrapped with that tool's public key; the data block is shared. Removing a recipient means encrypting again.

ryusim-protect list-recipients
Known Vendor Recipients: Vendor Key Owner Key Name Notes ------------------------------------------------------------------------------------------ ryusim Seiraiyu RyuSim-RSA-2026-01 RyuSim's own key vivado Xilinx xilinxt_2019_11 AMD/Xilinx Vivado questa Mentor Graphics Corporation MGC-VERIF-SIM-RSA-2 Siemens Questa quartus Altera Corporation Altera-Quartus-RSA-1 Intel Quartus vcs Synopsys SNPS-VCS-RSA-2 Synopsys VCS xcelium Cadence Design Systems. CDS-RSA-KEY-VER-1 Cadence Xcelium

Only RyuSim's public key ships with RyuSim. For another registry ID, ryusim-protect looks for <Key Owner>_<Key Name>_public.pem in the ieee1735/ directory next to its bin/ and in ~/.ryusim/ieee1735/, and stops if the file is not there:

Error: Public key for 'vivado' not found. Searched: ... For non-RyuSim recipients, use --recipient-key with the vendor's public key file.

Get the vendor's public key from the vendor, then either copy it to that name in ~/.ryusim/ieee1735/ or give it directly with --recipient-key, --recipient-owner and --recipient-name (one of each per recipient):

ryusim-protect encrypt --input my_ip.sv --output my_ip.svp \ --recipient ryusim \ --recipient-key Xilinx_xilinxt_2019_11_public.pem \ --recipient-owner Xilinx --recipient-name xilinxt_2019_11

ryusim-protect keygen --key-owner <owner> --key-name <name> writes an RSA-2048 key pair (<owner>_<name>_private.pem and <owner>_<name>_public.pem) for testing a flow with your own key.

Check and inspect a file

validate checks that every required envelope field is present. It does not decrypt anything or check the base64 data.

ryusim-protect validate my_ip.svp
VALID: All required envelope fields present.

A file with fields missing prints INVALID: Missing required fields: … and exits with status 1. info prints the envelope metadata, including every key owner, so you can see which tools a file was encrypted for:

ryusim-protect info my_ip.svp
Version: 2 Encrypt Agent: ryusim-protect Data Method: aes-256-gcm Key Owners (1): - Seiraiyu Key Names (1): - RyuSim-RSA-2026-01 IV: 6edaf810bbba88ae4b301dfc

Security model

ThreatWhat RyuSim does
Opening the .svp in an editorThe body is AES-encrypted.
Extracting the private key from the simulatorRyuSim does not contain it. The broker unwraps session keys server-side.
Padding-oracle attacks and modified payloadsAES-256-GCM rejects a modified payload or tag. Files using AES-128-CBC get no such check.
Generated C++ left on diskThe .cpp files of protected modules are deleted after a successful build. Their headers and object files remain.
Decrypted source left in memoryRyuSim zeroes its working copies. The parser's copy lives until the compile process exits.
Abuse of the brokerThe broker rate-limits requests; with an API key the limit is per key, without one per client address. It answers HTTP 403 to an API key it does not recognize.
A compromised keyKeys are versioned by name (RyuSim-RSA-2026-01), and the broker can hold more than one at once.

Limits

Troubleshooting

The messages below are printed by ryusim compile, which then prints Error: Compilation failed and exits with status 1.

error: no IEEE 1735 key block for key_owner="Seiraiyu" in <file>

The file was encrypted for other tools but not for RyuSim. ryusim-protect info lists the key owners it has. Ask the IP vendor for a file encrypted with --recipient ryusim. The check runs before any network request, so this reproduces offline:

sed 's/key_keyowner = "Seiraiyu"/key_keyowner = "Acme"/' my_ip.svp > acme.svp ryusim compile --top my_ip acme.svp 2>&1 || true

error: unsupported data_method "<method>" in <file>

The envelope names a cipher RyuSim does not decrypt (see Data methods). Ask the vendor for an aes-256-gcm file. Also checked before any network request:

sed 's/data_method = "aes-256-gcm"/data_method = "des-cbc"/' my_ip.svp > des.svp ryusim compile --top my_ip des.svp 2>&1 || true

fatal: cannot connect to <broker-url> — network required for encrypted IP (<reason>; 3 attempts)

No attempt reached the broker. The reason in parentheses comes from libcurl: a DNS failure, a refused or timed-out connection (a firewall that blocks HTTPS on port 443), or a TLS certificate that did not verify. Check the network, the proxy settings and --broker-url. curl -s https://broker.ryusim.com/health shows whether the broker is up. Behind a proxy that inspects TLS, add the proxy's CA certificate to the system trust store. The three attempts take about 6 seconds when the connection is refused, as with this unused local port:

ryusim compile --top my_ip --broker-url http://127.0.0.1:9/api/v1 my_ip.svp 2>&1 || true
fatal: cannot connect to http://127.0.0.1:9/api/v1 — network required for encrypted IP (Couldn't connect to server; 3 attempts) Error: Compilation failed

fatal: broker rejected request (HTTP 403) — check broker.ryusim.com status

The broker refused the request. It does this when RYUSIM_API_KEY holds a key it does not recognize. Unset the variable or fix the key. If it persists, contact Seiraiyu support.

fatal: no IEEE 1735 key block found for key_owner="Seiraiyu", key_name="<name>"

The broker has no private key under that name (HTTP 404). The file was encrypted with a RyuSim key the broker does not hold; ryusim-protect info shows the key name.

fatal: broker error (HTTP <code>): <body>

Any other non-200 answer. HTTP 429 means the rate limit was hit; the body says how many seconds to wait.

fatal: integrity check failed for encrypted IP in <file> — file may be tampered

The AES-256-GCM tag does not match: the file changed after it was encrypted, through corruption in transfer or on purpose. Download it again, compare its SHA-256 checksum if the vendor publishes one, and transfer it in binary mode (not FTP ASCII mode). If it still fails, contact the IP vendor. This check runs after the broker returns the session key.

error: <file>:<line>: malformed IEEE 1735 envelope — missing key_block and data_block

A protected region ends without a key block or a data block. A region that never ends gives error: <file>: unterminated pragma protect region. Run ryusim-protect validate <file> to see which fields are missing, and get the file from the vendor again.

For tool and IP vendors

For encrypted SystemVerilog to compile in RyuSim, its envelope needs a key block with key_keyowner="Seiraiyu" and key_keyname="RyuSim-RSA-2026-01", wrapped with RyuSim's public key. The release archive's ieee1735/ directory (installed to <prefix>/ieee1735/) holds:

{ "tool": "RyuSim", "vendor": "Seiraiyu", "key_owner": "Seiraiyu", "key_name": "RyuSim-RSA-2026-01", "key_method": "rsa", "key_size": 2048, "supported_data_methods": ["aes-256-gcm", "aes-128-cbc"], "supported_languages": ["verilog", "systemverilog"], "public_key_file": "Seiraiyu_RyuSim-RSA-2026-01_public.pem", "broker_url": "https://broker.ryusim.com/api/v1/unwrap" }

Use aes-256-gcm as the data_method.

To add your tool to the ryusim-protect list-recipients registry, email ip-protection@seiraiyu.com with the tool and vendor name, the key_keyowner and key_keyname strings your tool expects, and your RSA public key in PEM format. Seiraiyu checks the key by encrypting a test file that your tool must decrypt, then adds the tool in the next release.