🔑

Lit ProtocolのLitActionsを学ぶ

2024/12/27に公開

LitActions

前回の記事で暗号化とアクセス制御について学びましたが、今回はオフチェーンでのアクセス制御に関するLitActiosを深ぼっていきたいと思います。

最初に要点をまとめると、 「javascriptコードをIPFSに保存して、そのアドレスをACCに含め、LitNodeで実行することで、あたかもオフチェーンのjsコードをコントラクトのように扱うことができる」 というのが画期的なポイントかなと思ってます。

Litの暗号化とアクセス制御についてまだよくわかんないよって方はぜひ前回の記事も読んでみてください。
https://zenn.dev/shodaimomiyama/articles/be295a5049d807

また、この記事は以下のLit公式Docsをもとに作成しています。
https://developer.litprotocol.com/sdk/serverless-signing/quick-start

Quick Start

百聞は一見に如かずなので、まずは使ってみましょう。

Lit Actionの作成と実行

Lit Actionを作成し、実行する手順

  1. Litネットワークへの接続
  2. Lit Actionコードの作成とデプロイ
  3. Lit Actionの実行

Datil-devネットワークを使用して進めます。このネットワークは、開発者がLit SDKに慣れるために設計されており、無料で利用可能です。実運用向けのアプリケーションを構築する場合は、Datil-testネットワークを使用し、準備が整ったら**Datil(本番ネットワーク)**にデプロイできます。

Lit SDKのインストール

以下のパッケージをインストールします:

npm

npm install @lit-protocol/lit-node-client \
@lit-protocol/constants \
@lit-protocol/auth-helpers \
ethers@v5

yarn

yarn add @lit-protocol/lit-node-client \
@lit-protocol/constants \
@lit-protocol/auth-helpers \
ethers@v5

注意:
Node.js v19.9.0以上を使用してください。

  • cryptoサポート
  • webcryptoライブラリサポート(ウェブ向け)

ウォークスルー


1. Litネットワークへの接続

Lit Actionを実行するには、Litネットワークへの接続が必要です。これを行うには、LitNodeClientインスタンスを初期化し、ノードへの接続を確立します。

さらに、この例ではEthereumウォレットを初期化します。これは、セッション署名を生成するために使用されます。

import { LitNodeClient } from "@lit-protocol/lit-node-client";
import { LIT_NETWORK, LIT_RPC } from "@lit-protocol/constants";
import * as ethers from "ethers";

const litNodeClient = new LitNodeClient({
  litNetwork: LIT_NETWORK.DatilDev,
  debug: false
});
await litNodeClient.connect();

const ethersWallet = new ethers.Wallet(
  process.env.ETHEREUM_PRIVATE_KEY, // Replace with your private key
  new ethers.providers.JsonRpcProvider(LIT_RPC.CHRONICLE_YELLOWSTONE)
);

2. セッション署名の生成

セッション署名は、Litネットワークでの認証と接続維持に使用されます。これらは、Lit Actionの実行や署名などの機能を使用する際に必須です。

セッション署名に関して、まだよくわからないよって方は以下の記事を見てみてください。
https://zenn.dev/shodaimomiyama/articles/27d505aa777225

今回はコード例だけ示します。

import { LIT_ABILITY } from "@lit-protocol/constants";
import {
  LitActionResource,
  createSiweMessage,
  generateAuthSig,
} from "@lit-protocol/auth-helpers";

const sessionSignatures = await litNodeClient.getSessionSigs({
  chain: "ethereum",
  expiration: new Date(Date.now() + 1000 * 60 * 10).toISOString(), // 10 minutes
  resourceAbilityRequests: [
    {
      resource: new LitActionResource("*"),
      ability: LIT_ABILITY.LitActionExecution,
    },
  ],
  authNeededCallback: async ({
    uri,
    expiration,
    resourceAbilityRequests,
  }) => {
    const toSign = await createSiweMessage({
      uri,
      expiration,
      resources: resourceAbilityRequests,
      walletAddress: await ethersWallet.getAddress(),
      nonce: await litNodeClient.getLatestBlockhash(),
      litNodeClient,
    });

    return await generateAuthSig({
      signer: ethersWallet,
      toSign,
    });
  },
});

3. Lit Actionコードの保存

Lit Actionコードは、以下の2つの方法で保存できます:

  • インラインコードとして記述
  • IPFSを使用して保存

この例では、インラインコードを使用します。

const _litActionCode = async () => {
  if (magicNumber >= 42) {
      LitActions.setResponse({ response:"The number is greater than or equal to 42!" });
  } else {
      LitActions.setResponse({ response: "The number is less than 42!" });
  }
}

const litActionCode = `(${_litActionCode.toString()})();`;

4. Lit Actionの実行

executeJs関数を使用してLit Actionを実行します。この関数に以下のパラメータを渡します:

  • sessionSigs: セッション署名
  • code: Lit Actionコード(またはIPFSの場合はipfsId
  • jsParams(オプション): Lit Actionに渡すパラメータ

IPFSを使用する場合は、codeの代わりにipfsId(IPFS CID)を指定します。

const response = await litNodeClient.executeJs({
  sessionSigs: sessionSignatures,
  code: litActionCode,
  jsParams: {
    magicNumber: 43,
  }
});

LitActionsのデプロイ

Quick Startで何となくLitActionsの使い方がイメージできました。
このセクションでは、LitActionsを利用するための二つのデプロイ方法を紹介します。

LitノードがLit Actionを実行するには、そのコードにアクセスできる必要があります。
それには、以下の2つの方法があります:

・コード文字列を直接提供
・IPFSにアップロード


Lit Actionの制約

以下の制約が設けられており、これによりDoS攻撃やリソースの過剰消費を防ぎます。

時間制限
Datil/Datil-testネットワーク: 最大30秒
Datil-devネットワーク: 最大60秒
サイズ制限
最大100MB
コードのサイズが大きい場合、コードミニファイアを活用してください。
メモリ使用量
最大256MB


デプロイ方法の比較

  1. コード文字列を直接提供
    推奨される方法です。以下の特徴があります:

直接提供
Litノードに直接コードを渡すため、IPFS経由での取得による遅延や可用性の問題を回避できます。

信頼性
IPFSに依存しないため、常にコードが利用可能です。

欠点
ネットワーク使用量の増加: Lit Actionコード全体をリクエストごとに送信するため、ネットワーク使用量が増え、実行時間が長くなる可能性があります。
透明性の欠如: ユーザーがコードを確認しにくい場合があります。

  1. IPFSへのアップロード
    特定のケースで有効です。以下の特徴があります:

大規模コード
コードが大きい場合、IPFSに保存することでリクエストサイズを管理できます。

コードの再利用
複数のプロジェクトで同じコードを使用する場合、IPFSのCIDを参照するだけで済みます。

バージョン管理
IPFSはコンテンツベースのアドレス指定を使用しているため、変更ごとに新しいCIDが生成されます。

分散ストレージ
完全な分散型アプリケーションを目指す場合に適しています。

欠点
IPFSのネットワーク遅延: IPFSネットワークの応答が遅れる場合があります。
伝播時間: IPFSへのアップロード直後に、Litノードがコードを取得できない場合があります。
追加のネットワーク呼び出し: 各LitノードがIPFSからコードを取得する必要があるため、実行時間が増加します。


デプロイ方法の実装

コード文字列を直接提供する方法

以下は、コード文字列を直接渡してLit Actionを実行する例です。

const litActionCode = `
(async () => {
  console.log("This is my Lit Action!");
})();
`;

await litNodeClient.executeJs({
    sessionSigs,
    code: litActionCode,
});

litActionCode: 直接実行する即時関数としてコードを記述します。
executeJs関数: Litネットワークにリクエストを送信し、コードを実行します。

IPFSにアップロードする方法

以下は、IPFSにアップロードされたコードを使用する例です。

await litNodeClient.executeJs({
    sessionSigs,
    ipfsId: process.env.LIT_ACTION_IPFS_CID,
});

ipfsId: IPFSにアップロードされたコードのCIDを指定します。


イミュータビリティ(不変性)に関する注意

Ethereumのスマートコントラクトの特徴の一つに、イミュータビリティ(不変性) が挙げられます。ほとんどのスマートコントラクトでは、特定のアドレスに存在するコードが変更されることは決してなく、悪意のあるコードに置き換えられる心配はありません。Lit Actionsを使用する場合も、同様のセキュリティ保証を提供することが可能です。

IPFSを使用した場合のイミュータビリティ

Lit ActionコードをIPFSにアップロードする場合、この不変性がどのように実現されるかを簡単に説明できます。
IPFSはコンテンツベースのアドレス指定を採用しており、Lit Actionの コンテンツID(CID) がその内容に基づいて直接生成されます。そのため、コードにわずかな変更があっても完全に異なるCIDが生成されます。
これにより、Ethereumスマートコントラクトのアドレスと同様に、ユーザーはLitネットワークへのリクエスト時に使用されるIPFS CIDを確認して検証できます。


コード文字列を直接渡す場合の問題

一方、Lit Actionをコード文字列として渡す場合、イミュータビリティの保証はすぐには明確ではありません。
例えば、executeJsを呼び出す際に、開発者が悪意のあるコードを渡し、アプリケーションに対するユーザーの信頼を損なう可能性はどう防ぐのでしょうか?
ここで、許可されたAuth Methodがセキュリティを維持する上で重要な役割を果たします。


PKP(Programmable Key Pairs)によるセキュリティ強化

Programmable Key Pairs(PKP) を使用することで、特定のLit ActionだけがPKPを利用できるように設定できます。これには、信頼できるLit Actionに対応するIPFS CIDを許可されたAuth Methodとして追加します。

  • ユーザーの許可プロセス:
    • ユーザーは、信頼できるLit ActionのIPFS CIDを許可することができます。
    • 許可されたCIDに対応するLit ActionのみがPKPを利用して署名などの操作を実行できます。
    • それ以外のLit Actionコードを使用しようとすると、Litノードによって「未許可の使用エラー」が返されます。

IPFS CIDの取得

IPFSにアップロードせずとも、ipfs-only-hashのようなパッケージを使用して、任意のLit ActionコードのIPFS CIDを取得できます。
その後、ユーザーまたは開発者が信頼するLit ActionのCIDを、PKPで使用可能なAuth Methodとして許可します。

  • コード文字列の送信時の検証プロセス:
    • Litネットワークにコード文字列としてLit Actionを送信すると、各Litノードがそのコード文字列からIPFS CIDを生成します。
    • 生成されたCIDがPKPの許可されたAuth Methodに含まれているかどうかをチェックします。

このようにして、コード文字列を使用した場合でも、セキュリティと信頼性を確保できます。

Advanced Topic

Lit Actionsの基本的な使い方と考え方がわかったところで、実践ではどのような利用方法があるのかみていきましょう!

Lit Action内での復号


概要

Litを使用した復号は通常、認証されたユーザーがクライアント側でアクセス時に実行します。しかし、Lit Actionsを使用することで、代替的な復号方法も利用可能です。特に、decryptAndCombine関数を使用すると、Lit Action内でデータを復号できます。これにより、各Litノードの**Trusted Execution Environment (TEE)**内でデータを非公開のまま操作することが可能になります。

decryptAndCombineを呼び出すと、各ノードから復号シェアが収集され、単一のノードでこれらが組み合わされて、指定されたコンテンツが復号されます。

以下のガイドでは、クライアント側で文字列を暗号化し、その後Lit Actionを使用して復号するプロセスを説明します。ページの最後には、復号したAPIキーを使用してリモートAPIコールを実行する完全な例を紹介します。


コンテンツの暗号化

まずはデータを暗号化します。この操作は、Lit Action外部でクライアント側で実行されます。

const chain = 'ethereum';
const accessControlConditions = [
  {
    contractAddress: '',
    standardContractType: '',
    chain,
    method: 'eth_getBalance',
    parameters: [':userAddress', 'latest'],
    returnValueTest: {
      comparator: '>=',
      value: '0',
    },
  },
];

const message = 'Hello world';
const client = new LitNodeClient({
  litNetwork: "datil-dev"
});
await client.connect();

const { ciphertext, dataToEncryptHash } = await LitJsSdk.encryptString(
  {
    accessControlConditions,
    sessionSigs: {}, // セッション情報
    chain,
    dataToEncrypt: message,
  },
  client
);

console.log("暗号化されたデータ:", ciphertext, "ハッシュ:", dataToEncryptHash);

手順の詳細

  1. アクセス制御条件 (ACC) の設定
    ACCを作成し、誰がデータを復号できるか、またはどの条件で復号できるかを指定します。

  2. 暗号化の実行
    encryptString関数を使用して静的コンテンツ(文字列、ファイル、ZIPなど)を暗号化します。この操作でciphertextdataToEncryptHashが生成されます。これらはストレージプロバイダー(例: IPFS)に保存してください。


IPFS CIDをアクセス制御パラメータとして使用

例では、アクセス制御パラメータとしてcurrentActionIpfsIdを設定できます。これにより、特定のLit Action(IPFS CIDで識別)がデータを復号可能になります。これにより、APIキーなどの機密情報を特定のLit Actionだけが復号できるようになります。

{
  contractAddress: '',
  standardContractType: '',
  chain: 'ethereum',
  method: '',
  parameters: [':currentActionIpfsId'],
  returnValueTest: {
    comparator: '=',
    value: '<YOUR_LIT_ACTION_IPFS_CID>',
  },
}

decryptAndCombineの使用

暗号化されたデータ(ciphertextdataToEncryptHash)をLit Actionに渡して復号します。

const code = `(async () => {
  const resp = await Lit.Actions.decryptAndCombine({
    accessControlConditions,
    ciphertext,
    dataToEncryptHash,
    authSig: null, // セッションのauthSigを使用
    chain: 'ethereum',
  });

  Lit.Actions.setResponse({ response: resp });
})();`;

const res = await client.executeJs({
  code,
  sessionSigs: {}, // セッション情報
  jsParams: {
    accessControlConditions,
    ciphertext,
    dataToEncryptHash
  }
});

console.log("Lit Actionから送信された復号データ:", res);

詳細

  • authSigの指定: nullを指定すると、executeJsで提供されたauthSigが使用されます。

Discussion