Oasisは、プライバシーを確保しながらオンチェーンとの信頼を維持しつつ、オフチェーンでアプリを構築・実行するためのランタイムオフチェーンロジック(ROFL)フレームワークを導入しましたOasisは、プライバシーを確保しながらオンチェーンとの信頼を維持しつつ、オフチェーンでアプリを構築・実行するためのランタイムオフチェーンロジック(ROFL)フレームワークを導入しました

Oasis ROFLを使用したクロスチェーン鍵生成(EVM / Base)ガイド

2026/02/20 21:16
19 分で読めます

Oasisは、オフチェーンでアプリを構築・実行しながらプライバシーを確保し、オンチェーン検証可能性で信頼を維持するために、ランタイムオフチェーンロジック(ROFL)のフレームワークを導入しました。ROFLでの構築には多くの可動部分があります。
このチュートリアルでは、ROFL内でsecp256k1鍵を生成する小さなTypeScriptアプリの構築方法をデモします。これは、内部でappd REST APIと通信する@oasisprotocol/rofl-client TypeScript SDKを使用します。このTypeScriptアプリは以下も実行します:

ログに出力するシンプルなスモークテストも含まれます。

前提条件

このガイドで説明されている手順を実行するには、以下が必要です:

  • Node.js 20+およびDocker(またはPodman)
  • Oasis CLIおよびウォレットに最低120 TESTトークン(Oasis テストネットfaucet)
  • Base Sepiolaテスト用ETH(Base Sepiola faucet)

セットアップの詳細については、クイックスタート前提条件に関するドキュメントを参照してください。

アプリの初期化

最初のステップは、Oasis CLIを使用して新しいアプリを初期化することです。

oasis rofl init rofl-keygen
cd rofl-keygen

アプリの作成

テストネット上でアプリを作成する際、トークンのデポジットが必要になります。この時点で100 TESTトークンを割り当てます。

oasis rofl create --network testnet

出力として、CLIはrofl1…で示されるApp IDを生成します。

Hardhat(TypeScript)プロジェクトの初期化

これでプロジェクトを開始する準備が整いました。

npx hardhat init

TypeScriptアプリを紹介しているので、プロンプトが表示されたらTypeScriptを選択し、その後デフォルト設定を受け入れます。
次のステップは、Hardhat外部で使用するための小さなランタイム依存関係を追加することです。

npm i @oasisprotocol/rofl-client ethers dotenv @types/node
npm i -D tsx

HardhatのTypeScriptテンプレートは自動的にtsconfig.jsonを作成します。アプリコードをdist/にコンパイルできるように、小さなスクリプトを追加する必要があります。

// tsconfig.json
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}

アプリの構造

このセクションでは、いくつかの小さなTSファイルと1つのSolidityコントラクトアドレスを追加します。

src/
├── appd.ts # @oasisprotocol/rofl-clientの薄いラッパー
├── evm.ts # ethersヘルパー(プロバイダー、ウォレット、トランザクション、デプロイ)
├── keys.ts # 小さなヘルパー(チェックサム)
└── scripts/
├── deploy-contract.ts # コンパイルされたアーティファクト用の汎用デプロイスクリプト
└── smoke-test.ts # エンドツーエンドデモ(ログ)
contracts/
└── Counter.sol # サンプルコントラクトアドレス

  1. src/appd.ts — SDK上の薄いラッパー ここでは、appd(UNIXソケット)と通信するために公式クライアントを使用する必要があります。また、ROFL外部で実行する際の明示的なlocal‑devフォールバックも維持する必要があります。

src/appd.ts

import {existsSync} from 'node:fs';
import {
RoflClient,
KeyKind,
ROFL_SOCKET_PATH
} from '@oasisprotocol/rofl-client';
const client = new RoflClient(); // UDS: /run/rofl-appd.sock
export async function getAppId(): Promise<string> {
return client.getAppId();
}
/**
* ROFL内でsecp256k1鍵を生成(または決定論的に再導出)し、
* ethers.js Wallet用の0xプレフィックス付き16進文字列として返します。
*
* ローカル開発のみ(ROFL外部):ソケットが存在せず、
* ALLOW_LOCAL_DEV=trueおよびLOCAL_DEV_SK=0x<64-hex>を設定した場合、その値が使用されます。
*/
export async function getEvmSecretKey(keyId: string): Promise<string> {
if (existsSync(ROFL_SOCKET_PATH)) {
const hex = await client.generateKey(keyId, KeyKind.SECP256K1);
return hex.startsWith('0x') ? hex : `0x${hex}`;
}
const allow = process.env.ALLOW_LOCAL_DEV === 'true';
const pk = process.env.LOCAL_DEV_SK;
if (allow && pk && /^0x[0-9a-fA-F]{64}$/.test(pk)) return pk;
throw new Error(
'rofl-appd socket not found and no LOCAL_DEV_SK provided (dev only).'
);
}

2. src/evm.ts — ethersヘルパー

import {
JsonRpcProvider,
Wallet,
parseEther,
type TransactionReceipt,
ContractFactory
} from "ethers";
export function makeProvider(rpcUrl: string, chainId: number) {
return new JsonRpcProvider(rpcUrl, chainId);
}
export function connectWallet(
skHex: string,
rpcUrl: string,
chainId: number
): Wallet {
const w = new Wallet(skHex);
return w.connect(makeProvider(rpcUrl, chainId));
}
export async function signPersonalMessage(wallet: Wallet, msg: string) {
return wallet.signMessage(msg);
}
export async function sendEth(
wallet: Wallet,
to: string,
amountEth: string
): Promise<TransactionReceipt> {
const tx = await wallet.sendTransaction({
to,
value: parseEther(amountEth)
});
const receipt = await tx.wait();
if (receipt == null) {
throw new Error("Transaction dropped or replaced before confirmation");
}
return receipt;
}
export async function deployContract(
wallet: Wallet,
abi: any[],
bytecode: string,
args: unknown[] = []
): Promise<{ address: string; receipt: TransactionReceipt }> {
const factory = new ContractFactory(abi, bytecode, wallet);
const contract = await factory.deploy(...args);
const deployTx = contract.deploymentTransaction();
const receipt = await deployTx?.wait();
await contract.waitForDeployment();
if (!receipt) {
throw new Error("Deployment TX not mined");
}
return { address: contract.target as string, receipt };
}

3. src/keys.ts — 小さなヘルパー

import { Wallet, getAddress } from "ethers";
export function secretKeyToWallet(skHex: string): Wallet {
return new Wallet(skHex);
}
export function checksumAddress(addr: string): string {
return getAddress(addr);
}

4. src/scripts/smoke-test.ts — 単一のエンドツーエンドフロー

このスクリプトには複数の機能があるため、重要なステップです:

  • App ID(ROFL内)、アドレス、署名されたメッセージを出力
  • 資金提供を待機
  • カウンターコントラクトアドレスをデプロイ

import "dotenv/config";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { getAppId, getEvmSecretKey } from "../appd.js";
import { secretKeyToWallet, checksumAddress } from "../keys.js";
import { makeProvider, signPersonalMessage, sendEth, deployContract } from "../evm.js";
import { formatEther, JsonRpcProvider } from "ethers";
const RPC_URL = process.env.BASE_RPC_URL ?? "https://sepolia.base.org";
const CHAIN_ID = Number(process.env.BASE_CHAIN_ID ?? "84532");
const KEY_ID = process.env.KEY_ID ?? "evm:base:sepolia";
function sleep(ms: number): Promise<void> {
return new Promise((r) => setTimeout(r, ms));
}
async function waitForFunding(
provider: JsonRpcProvider,
addr: string,
minWei: bigint = 1n,
timeoutMs = 15 * 60 * 1000,
pollMs = 5_000
): Promise<bigint> {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const bal = await provider.getBalance(addr);
if (bal >= minWei) return bal;
console.log(`Waiting for funding... current balance=${formatEther(bal)} ETH`);
await sleep(pollMs);
}
throw new Error("Timed out waiting for funding.");
}
async function main() {
const appId = await getAppId().catch(() => null);
console.log(`ROFL App ID: ${appId ?? "(unavailable outside ROFL)"}`);
const sk = await getEvmSecretKey(KEY_ID);
// 注:このデモは設定されたRPCプロバイダーを信頼します。本番環境では、
// ライトクライアント(例:Helios)を使用して、リモートチェーンの状態を検証することをお勧めします。
const wallet = secretKeyToWallet(sk).connect(makeProvider(RPC_URL, CHAIN_ID));
const addr = checksumAddress(await wallet.getAddress());
console.log(`EVM address (Base Sepolia): ${addr}`);
const msg = "hello from rofl";
const sig = await signPersonalMessage(wallet, msg);
console.log(`Signed message: "${msg}"`);
console.log(`Signature: ${sig}`);
const provider = wallet.provider as JsonRpcProvider;
let bal = await provider.getBalance(addr);
if (bal === 0n) {
console.log("Please fund the above address with Base Sepolia ETH to continue.");
bal = await waitForFunding(provider, addr);
}
console.log(`Balance detected: ${formatEther(bal)} ETH`);
const artifactPath = join(process.cwd(), "artifacts", "contracts", "Counter.sol", "Counter.json");
const artifact = JSON.parse(readFileSync(artifactPath, "utf8"));
if (!artifact?.abi || !artifact?.bytecode) {
throw new Error("Counter artifact missing abi/bytecode");
}
const { address: contractAddress, receipt: deployRcpt } =
await deployContract(wallet, artifact.abi, artifact.bytecode, []);
console.log(`Deployed Counter at ${contractAddress} (tx=${deployRcpt.hash})`);
console.log("Smoke test completed successfully!");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

5. contracts/Counter.sol — 最小限のサンプル

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Counter {
uint256 private _value;
event Incremented(uint256 v);
event Set(uint256 v);
function current() external view returns (uint256) { return _value; }
function inc() external { unchecked { _value += 1; } emit Incremented(_value); }
function set(uint256 v) external { _value = v; emit Set(v); }
}

6. src/scripts/deploy-contract.ts — 汎用デプロイヤー

import "dotenv/config";
import { readFileSync } from "node:fs";
import { getEvmSecretKey } from "../appd.js";
import { secretKeyToWallet } from "../keys.js";
import { makeProvider, deployContract } from "../evm.js";
const KEY_ID = process.env.KEY_ID ?? "evm:base:sepolia";
const RPC_URL = process.env.BASE_RPC_URL ?? "https://sepolia.base.org";
const CHAIN_ID = Number(process.env.BASE_CHAIN_ID ?? "84532");
/**
* 使用方法:
* npm run deploy-contract -- ./artifacts/MyContract.json '[arg0, arg1]'
* アーティファクトには { abi, bytecode } が含まれている必要があります。
*/
async function main() {
const [artifactPath, ctorJson = "[]"] = process.argv.slice(2);
if (!artifactPath) {
console.error("Usage: npm run deploy-contract -- <artifact.json> '[constructorArgsJson]'");
process.exit(2);
}
const artifactRaw = readFileSync(artifactPath, "utf8");
const artifact = JSON.parse(artifactRaw);
const { abi, bytecode } = artifact ?? {};
if (!abi || !bytecode) {
throw new Error("Artifact must contain { abi, bytecode }");
}
let args: unknown[];
try {
args = JSON.parse(ctorJson);
if (!Array.isArray(args)) throw new Error("constructor args must be a JSON array");
} catch (e) {
throw new Error(`Failed to parse constructor args JSON: ${String(e)}`);
}
const sk = await getEvmSecretKey(KEY_ID);
// 注:このデモは設定されたRPCプロバイダーを信頼します。本番環境では、
// ライトクライアント(例:Helios)を使用して、リモートチェーンの状態を検証することをお勧めします。
const wallet = secretKeyToWallet(sk).connect(makeProvider(RPC_URL, CHAIN_ID));
const { address, receipt } = await deployContract(wallet, abi, bytecode, args);
console.log(JSON.stringify({ contractAddress: address, txHash: receipt.hash, status: receipt.status }, null, 2));
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

Hardhat(コントラクトアドレスのみ)

この段階で、Counter.solをコンパイルするための最小限の設定が必要です

hardhat.config.ts

import type { HardhatUserConfig } from "hardhat/config";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.24",
settings: {
optimizer: { enabled: true, runs: 200 }
}
},
paths: {
sources: "./contracts",
artifacts: "./artifacts",
cache: "./cache"
}
};
export default config;

注目すべき点は、ローカルコンパイルはオプションであるため、スキップすることもできます。次のステップは選択です — 既存のcontracts/Lock.solファイルを削除するか、Solidityのバージョン0.8.24に更新できます。

npx hardhat compile

コンテナ化

これは重要なステップです。ここでは、TSをビルドし、コントラクトアドレスをコンパイルするDockerfileが必要です。このファイルはスモークテストを一度実行し、その後ログを検査している間はアイドル状態になります。

Dockerfile

FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
COPY contracts ./contracts
COPY hardhat.config.ts ./
RUN npm run build && npx hardhat compile && npm prune --omit=dev
ENV NODE_ENV=production
CMD ["sh", "-c", "node dist/scripts/smoke-test.js || true; tail -f /dev/null"]

次に、ROFLによって提供されるappdソケットをマウントする必要があります。このプロセスでは公開ポートが公開されないので安心してください。

compose.yaml

services:
demo:
image: docker.io/YOURUSER/rofl-keygen:0.1.0
platform: linux/amd64
environment:
- KEY_ID=${KEY_ID:-evm:base:sepolia}
- BASE_RPC_URL=${BASE_RPC_URL:-https://sepolia.base.org}
- BASE_CHAIN_ID=${BASE_CHAIN_ID:-84532}
volumes:
- /run/rofl-appd.sock:/run/rofl-appd.sock

イメージのビルド

ROFLはIntel TDX対応ハードウェアでのみ動作することを覚えておくことが重要です。したがって、macOSなどの異なるホストでイメージをコンパイルしている場合、--platform linux/amd64パラメータを渡すことが必須の追加ステップになります。

docker buildx build --platform linux/amd64 \
-t docker.io/YOURUSER/rofl-keygen:0.1.0 --push .

ここで注目すべき興味深い点は、追加のセキュリティと検証可能性を選択できることです。ダイジェストをピン留めして、compose.yamlimage: …@sha256:…を使用するだけです。

ROFLバンドルのビルド

oasis rofl buildコマンドを実行する前に実行する必要があるステップがあります。イメージセグメントのビルドはコンテナ化の後に行われるため、compose.yamlservices.demo.imageをビルドしたイメージに更新する必要があります。
このような単純なTypeScriptプロジェクトの場合、イメージサイズが予想より大きくなる可能性があることがあります。したがって、rofl.yamlresourcesセクションを少なくとも次のように更新することをお勧めします:memory: 1024およびstorage.size: 4096
これで準備が整いました。

oasis rofl build

次に、エンクレーブIDと設定を公開できます。

oasis rofl update

デプロイ

これは、テストネットプロバイダーにデプロイする簡単なステップです。

oasis rofl deploy

エンドツーエンド(Base Sepolia)

これは2ステップのプロセスですが、2番目のステップはオプションです。
まず、スモークテストのログを表示します。

oasis rofl machine logs

ここまでのすべてのステップを正しく完了している場合、出力には次のものが表示されます:

  • App ID
  • EVMアドレスと署名されたメッセージ
  • アドレスに資金を提供するプロンプト
  • 資金提供が完了すると、Counter.solのデプロイ

次に、ローカル開発です。ここでは、TypeScriptコードとSolidityコントラクトアドレスをコンパイルするためにnpm run build:allを実行する必要があります。必要ない場合はこのステップをスキップしてください。

export ALLOW_LOCAL_DEV=true
export LOCAL_DEV_SK=0x<64-hex-dev-secret-key> # 本番環境では使用しないでください
npm run smoke-test

セキュリティと覚えておくべき注意事項

  • プロバイダーログは保存時に暗号化されません。したがって、秘密鍵を絶対にログに記録しないでください。
  • appdソケット/run/rofl-appd.sockROFL内部でのみ存在します。
  • パブリックRPCにはレート制限がある場合があります。したがって、専用のBase RPC URLを選択することをお勧めします。

Oasis GitHubには鍵生成デモがあり、このチュートリアルの例として参照できます。https://github.com/oasisprotocol/demo-rofl-keygen

これで、ROFLでappdを使用して鍵を生成し、メッセージに署名し、コントラクトアドレスをデプロイし、Base Sepolia上でETHを移動することに成功しました。コメント欄でフィードバックをお知らせください。特定の問題についてOasisエンジニアリングチームとすぐにチャットするには、公式Discordのdev-centralチャンネルにコメントをドロップできます。

2026/2/20にhttps://dev.toで初版公開。


Oasis ROFLを使用したクロスチェーン鍵生成(EVM / Base)ガイドは、Coinmonks on Mediumで最初に公開されました。ストーリーをハイライトし、返信することで、人々が会話を続けています。

市場の機会
CROSS ロゴ
CROSS価格(CROSS)
$0.09947
$0.09947$0.09947
-3.27%
USD
CROSS (CROSS) ライブ価格チャート
免責事項:このサイトに転載されている記事は、公開プラットフォームから引用されており、情報提供のみを目的としています。MEXCの見解を必ずしも反映するものではありません。すべての権利は原著者に帰属します。コンテンツが第三者の権利を侵害していると思われる場合は、削除を依頼するために crypto.news@mexc.com までご連絡ください。MEXCは、コンテンツの正確性、完全性、適時性について一切保証せず、提供された情報に基づいて行われたいかなる行動についても責任を負いません。本コンテンツは、財務、法律、その他の専門的なアドバイスを構成するものではなく、MEXCによる推奨または支持と見なされるべきではありません。