<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Yuren&apos;s Blog - 技術</title><description>撰寫就是一種思考方式，發佈與分享只是副產品，而真正的意義是在自我的知識脈絡裡面有了歸屬與連結。</description><link>https://yurenju.blog/</link><language>zh-Hant</language><item><title>OpenClaw 所開啟的大門</title><link>https://yurenju.blog/zh/posts/2026-02-06_the-door-opened-by-openclaw/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2026-02-06_the-door-opened-by-openclaw/</guid><pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;openclaw-logo.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;774&quot; height=&quot;427&quot; src=&quot;/_astro/openclaw-logo.DVrFFRTQ_JmPhF.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;「OpenClaw 跟其他 AI Bot 有什麼不同阿？」&lt;/p&gt;
&lt;p&gt;要回答這個問題可以從我第一個請他幫忙的事情開始：&lt;strong&gt;找我喜歡的咖啡館&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我自己對於咖啡有特定的喜好，以至於會需要在 Google 地圖上每間咖啡館點進去看菜單是否標示咖啡產地資訊、處理方式等等才能篩選出喜歡的咖啡館，所以這件事情一直都很困擾我，其他 Bot 也沒支援看 Google 地圖的店家照片功能。&lt;/p&gt;
&lt;p&gt;所以架好 OpenClaw 之後的第一個問題也是問附近的咖啡館。而下一個問題就是詢問是否能夠看店內的照片或是 Google 評論，這個時候他的回答就很有意思了。&lt;/p&gt;
&lt;p&gt;「目前只回傳基本資訊，沒有照片和評論內容。」&lt;/p&gt;
&lt;p&gt;「要的話我可以幫這個 skill 加上照片和評論功能，不過需要一點時間改 code。要弄嗎？」&lt;/p&gt;
&lt;p&gt;我看到回覆真是下巴掉下來，以前不管用 ChatGPT 或是 Gemini 時，可以客製化的空間有限，而從來沒遇過 Bot 可以修改自己的程式來解決我的問題，就像他拿著烙鐵打開自己的機械胸腔重新焊接一樣。&lt;/p&gt;
&lt;p&gt;最後跟他討論過後，這是產生的結果。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;bernard-cafe-scout.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;839&quot; height=&quot;1390&quot; src=&quot;/_astro/bernard-cafe-scout.CW-WyqDy_MwO64.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;回過頭來，如果要回答「OpenClaw 跟其他 AI Bot 的不同之處」，我會說 OpenClaw 就像是「有了一個軟體工程師的夥伴，而且給了他一台電腦」。除了利用電腦上現有的工具來解決問題之外，最重要的是他可以&lt;strong&gt;製作與修改工具&lt;/strong&gt;。如果他手上的工具不能看 Google 地圖的圖片，那他就修改程式加上看圖片的功能。&lt;/p&gt;
&lt;p&gt;現行大多的 AI 聊天機器人更多的是提供工具，而這些工具要先有其他人提供，使用者才可以存取特定服務。而 OpenClaw 即使沒有人提供工具，他也可以自己造出來。即使像 Claude Code 這樣的工具跟 OpenClaw 還是有點差別，Claude Code 也可以造工具，但是目的是要和你一起協作開發軟體。&lt;/p&gt;
&lt;p&gt;而 OpenClaw 製作工具的目的則是更通用的解決你的問題。&lt;/p&gt;
&lt;p&gt;接著我就開始嘗試解決自己的一些困擾。比如說我有看各國新聞的習慣，但是為了閱讀速度，我通常得找有中文版的國外新聞網站，比如說紐約時報、德國之聲、法廣等。但有了 OpenClaw 之後，我直接請他研究一下各個國家原文新聞網站有提供 RSS 的媒體，然後請他根據他對我的認識，從這些媒體裡面挑出我可能會有興趣的新聞翻譯成中文，然後每天早上製作一份語音的新聞簡報，這樣我早上起床之後就可以聽一下新聞。&lt;/p&gt;
&lt;p&gt;然後在附上評分機制，作為他下次篩選新聞的依據 😁&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;bernard-daily-news.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1288&quot; height=&quot;1417&quot; src=&quot;/_astro/bernard-daily-news.CXWOmuSN_Z2llxwL.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;當然這就繞回來那房間裡的大象，OpenClaw 真的很不安全嗎？&lt;/p&gt;
&lt;p&gt;這個也很容易想像，如果你給軟體工程師一台電腦（即使沒有管理者權限），然後衡量他能做出什麼事情、有多危險。可以詢問一下身旁的工程師朋友關於工程師圈的蠢事，不小心刪除資料庫、把 git 弄亂、砍掉根目錄等等，這個 Bot 都有可能作出來（即使模型本身有很多保護措施）。&lt;/p&gt;
&lt;p&gt;OpenClaw 就是差不多是這個危險程度之上，再加上還可能會被 Prompt Injection 導致劫持去作其他事情。以我的例子來說，如果有人在 RSS 或是評論裡面塞入惡意留言，我沒把握他可以很好的預防這類型的攻擊。&lt;/p&gt;
&lt;p&gt;所以我會說安全問題是挺嚴重的，特別是你沒有提供一個獨立的環境給他使用，而是把他跑在自己的電腦裡面，這樣安全問題就更大了。&lt;/p&gt;
&lt;p&gt;但這絲毫掩蓋不了 OpenClaw 是個充滿創意跟樂趣的實驗。如果看 Anthropic 或是 OpenAI 作實驗的方式就相對謹慎，希望在可控範圍內完成更多事情。而 OpenClaw 是反其道而行：如果我給 AI Bot 一台電腦，那會多有趣？&lt;/p&gt;
&lt;p&gt;這就跟溼婆跳舞一樣，是毀滅的開端，也是重生之時。&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>讓 Claude Code 自己做驗收測試</title><link>https://yurenju.blog/zh/posts/2025-07-22_claude-acceptance-test/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2025-07-22_claude-acceptance-test/</guid><pubDate>Tue, 22 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近幾個月一直在使用 Cursor 與 Claude Code 進行開發，並且一直在推進邊界，看 LLM 輔助可以到達什麼程度。覺得在這個過程裡面，也遇到大家經常遇到的問題：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;寫得很快，但時好時壞。好的時候很驚訝，壞的時候也很驚訝&lt;/li&gt;
&lt;li&gt;需求不夠清楚時，它會自行補足細節，而這些細節不見得是我要的&lt;/li&gt;
&lt;li&gt;LLM 寫得太快太多讓開發者認知過載，確認內容時總是忍不住想全盤接受&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;經過各種嘗試之後，從一個軟體開發者的角度，我找到了適合自己與 LLM 的工作方法，也就是回歸到驗收測試。經過這麼長時間的 AI 協同工作後，我發現跟 LLM 合作與跟真人工程師合作有許多相似之處：當需求越明確，討論得越多，通常可以產生更符合預期的產出。&lt;/p&gt;
&lt;p&gt;而需求要如何才能明確，就讓我想到剛入行時學習的一套框架 Cucumber 以及其語法 Gherkin。Cucumber 是一套 Behavior-driven development (BDD) 工具，他透過撰寫人類與機器皆可閱讀的文件作為驗收條件。比如說我們如果要開發一個 Todo 軟體，其中一個規格就是要可以按下 Enter 來送出待辦事項，使用 Gherkin 語法就可以這麼敘述：&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;gherkin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;  Scenario&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt; Add todo item&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;    When &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;I enter &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;&quot;Buy milk&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; in the input field&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;    And &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;I press the Enter key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;    Then &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;I should see &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;&quot;Buy milk&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; in the list&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;    And &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;the input field should be cleared&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但他要怎麼轉化成可自動執行的測試呢？通常要寫一段 glue code 來將規格銜接到測試邏輯：&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; Given&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; When&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; Then&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;@cucumber/cucumber&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;const&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; expect&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; require&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;@playwright/test&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#758575DD&quot;&gt;// Assume we have a page object to manipulate the browser&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;let&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; page&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;When&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;I enter {string} in the input field&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#758575DD&quot;&gt;  // Find the input field and enter text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; inputField&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; page&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;input[type=&quot;text&quot;]&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; inputField&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;fill&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;When&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;I press the Enter key&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; ()&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#758575DD&quot;&gt;  // Press Enter key in the input field&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; inputField&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; page&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;input[type=&quot;text&quot;]&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;  await&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; inputField&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;press&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;Enter&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Then&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;I should see {string} in the list&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt;expectedText&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#758575DD&quot;&gt;  // Verify that the todo item appears in the list&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; todoItems&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; page&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;.todo-item&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; itemTexts&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; todoItems&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;allTextContents&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;  expect&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;itemTexts&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;toContain&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;expectedText&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Then&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;the input field should be cleared&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; ()&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#758575DD&quot;&gt;  // Verify that the input field is cleared&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; inputField&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; page&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;locator&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;input[type=&quot;text&quot;]&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; value&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; inputField&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;inputValue&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;  expect&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;value&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;toBe&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以前我在幾個 side project 使用過 Cucumber，但是後來從來沒在 Production 的專案裡面用過，主要還是要導入這樣的機制並不容易，通常團隊可以接受 TDD 的就已經很少見了，更別說要從規格銜接到自動化測試。&lt;/p&gt;
&lt;p&gt;另外也跟我經常是在新創團隊工作有關，通常新創團隊不會有那麼長的時間可以實踐規格到測試的週期規劃。&lt;/p&gt;
&lt;p&gt;但其中最大的一個障礙就是撰寫 glue code 了，因為他是把每個句子拆開來寫成一段動作，所以一個測試場景會被拆成很多小片段，另外撰寫 gherkin 的時候也要很注意，要記得相同功能的句子要寫的一樣，才有辦法在 glue code 裡面被合併。比如說：&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;gherkin&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;When &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;I click the button &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;&quot;ok&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;When &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;I go to click the button &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;&quot;ok&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;這樣就會被拆成兩個不同的測試邏輯片段，要記得做相同的事情時，敘述要完全相同。&lt;/p&gt;
&lt;p&gt;總之使用 Cucumber 是個新奇有趣的體驗，但各種阻礙確實讓我沒有在 production 專案使用過 Cucumber。&lt;/p&gt;
&lt;p&gt;但到了 LLM 進行軟體開發的年代事情又不一樣了，因為 LLM 可以直接讀取 gherkin 撰寫的規格，然後&lt;strong&gt;直接執行，不需要寫 glue code&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;由於 LLM 可以直接閱讀以及理解規格，然後藉由  Model Context Protocol (MCP) 直接讓 Cursor 或 Claude Code 來操作瀏覽器、手機模擬器來輔助開發。這也代表我們可以用 gherkin 敘述預期的行為是怎樣後，LLM 可以透過 MCP 自行確認他的開發成果是否可以通過驗收。&lt;/p&gt;
&lt;p&gt;而 Gherkin 語法就可以當作一個很好的橋樑，他是一個標準語法可以讓人類與 LLM 都可以讀懂，所以我們就可以在開發前透過這份規格來確認實作內容，而在開發完成之後可以讓 LLM 執行閱讀這份規格，並且使用 MCP 操作瀏覽器、手機來進行驗收，詳細的展示可以看點選到下面的 Youtube 影片觀看。&lt;/p&gt;
&lt;p&gt;!youtube[WvGY_Jcm_kY]&lt;/p&gt;
&lt;p&gt;這樣除了可以拿來跟 LLM 溝通以外，當它發現不符合驗收條件時，也可以觀察並且修改實作。&lt;/p&gt;
&lt;p&gt;有興趣的話可以到 github 自己試試看： &lt;a href=&quot;https://github.com/yurenju/llm-bdd-coding-demo&quot;&gt;yurenju/llm-bdd-coding-demo&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;bdd--tdd&quot;&gt;BDD + TDD&lt;/h2&gt;
&lt;p&gt;BDD 可以透過更明確的規格以及驗收條件，降低產出結果不如預期的問題，但卻不能解決開發者&lt;strong&gt;認知過載&lt;/strong&gt;的問題。而加上循序漸近式的 TDD 可以緩解這個問題。&lt;/p&gt;
&lt;p&gt;當使用 BDD 時，已經可以很好的確定開發規格以及驗收標準，但是另外一個是 LLM 開發現在很經常遇到的狀況，就是 LLM 寫得太快了，當一次產出的內容大過我的認知負擔後，我就會經不起誘惑，直接按下 &lt;strong&gt;確定&lt;/strong&gt;，但有時候不仔細看總是會產出不是我想要的內容。&lt;/p&gt;
&lt;p&gt;為了解決這樣的認知負荷，我最近都在測試 BDD + TDD。BDD 的部分跟前面敘述的一樣使用 Gherkin 作為驗收標準。但我會請 LLM 拆解元件，並且在開發每一個元件時，遵守以下的順序：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先寫介面 (Interface)、空類別或是空函式，並且拋出未實作的錯誤如 &lt;code&gt;throw new Error(&apos;not implemented yet&apos;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;請它&lt;strong&gt;只寫測試敘述&lt;/strong&gt;，也就是自動化測試的 &lt;code&gt;describe(&apos;敘述&apos;)&lt;/code&gt; 與 &lt;code&gt;it(&apos;敘述&apos;)&lt;/code&gt;，並且讓我檢查，不要實作任何測試邏輯&lt;/li&gt;
&lt;li&gt;接下來我會知道它想要寫大概到什麼程度的測試，並且直接在這個階段跟他溝通測試的顆粒細度，通常我都會大砍測試項目，因為一般來說它會寫得太細&lt;/li&gt;
&lt;li&gt;確認測試項目之後，再請他寫測試邏輯&lt;/li&gt;
&lt;li&gt;執行測試，這個時候應該新增測試都要是錯誤的（紅燈階段）&lt;/li&gt;
&lt;li&gt;請它開始實作，並且在實作完後跑測試，理論上我們寫的測試最後要全部通過（綠燈階段）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在這樣的開發流程下，就可以確保每個階段的產出都在我的認知負荷內，我可以很好的確認它的產出，然後有明確的「什麼是對的」之後，跟 BDD 流程相同，在有明確的完成條件下它可以做得很好。&lt;/p&gt;
&lt;p&gt;如果你對這樣的開發流程有興趣，你可以參考我之前寫的 &lt;a href=&quot;https://github.com/yurenju/cursor-tdd-rules&quot;&gt;yurenju/cursor-tdd-rules&lt;/a&gt;，如果需要在 Claude Code 使用的話還需要稍微修改一下。&lt;/p&gt;
&lt;p&gt;不過請記住這些都是還在發展的合作協同開發方式，現在工具跟使用技巧更新的很快，或許很快就不適用了。&lt;/p&gt;
&lt;p&gt;使用的這樣開發方式，最主要的目的就是要降低自己的認知負擔，讓專案可以在自己的掌握下盡可能使用 LLM 來完成我的目的，同時也透過劃定邊界、目標的方式更好的跟 LLM 溝通自己的目標到底是什麼。&lt;/p&gt;
&lt;p&gt;而在這樣的過程中，我也覺得在開發初期就會更清楚自己想要什麼。跟 LLM 一起工作與跟人類工作的訣竅都差不多，就是要更頻繁的溝通與確認需求。&lt;/p&gt;
&lt;p&gt;所以或許跟與人類一起工作也沒什麼太大差別，加強自己的溝通能力就是了。&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>叫 AI 幫我寫程式，結果他聽不懂人話？</title><link>https://yurenju.blog/zh/posts/2025-04-23_ai-coding-doesnt-understand-me/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2025-04-23_ai-coding-doesnt-understand-me/</guid><pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近公司的一個新專案的其中一個子專案實驗了新的開發方式：嘗試絕大部分的程式都使用 AI (Cursor) 編寫，盡可能地減少人為介入，希望透過進行小範圍的嘗試來理解未來可能的軟體開發模式。&lt;/p&gt;
&lt;p&gt;我們也希望透過 AI 的協助可以讓我們大幅度的縮短開發時程，達到以下的效果：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;reduce-development-cycle.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;952&quot; height=&quot;620&quot; src=&quot;/_astro/reduce-development-cycle.DfsiRO6k_4RYfV.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;如果你也嘗試過在稍微大的專案這麼做，你可以猜到我們初期的結果是這樣：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;expected-and-actual.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;952&quot; height=&quot;620&quot; src=&quot;/_astro/expected-and-actual.DN7tC4id_G5Qat.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;只要想要讓 AI 多做一點事情時，就經常會無法照著預想做。大部分可以一次就完成的工作，通常都是簡潔明瞭，不容易有疑義的任務，這個通常一次就可以做得很好。&lt;/p&gt;
&lt;p&gt;當然我們不會因為這樣就不用 AI 進行開發了。就自己來說，現在有超過八九成的程式碼都是用 AI 開發的了，其中大量地使用對話的方式進行開發。而由於 AI 有深厚的軟體開發能力，而我可以認真地當一個監工，確保他的工作能夠如預期的完成。&lt;/p&gt;
&lt;p&gt;但到底要怎麼樣達到這一步？實際在專案導入 AI 前，得先回到平常在團隊裡面是怎麼開發軟體專案的。&lt;/p&gt;
&lt;h2 id=&quot;以往的軟體開發討論&quot;&gt;以往的軟體開發討論&lt;/h2&gt;
&lt;p&gt;在以往的軟體開發流程當中，團隊內經常會透過從產品面向進行策略上的討論，到工程團隊之後開始討論技術解決方案，經過了開會討論以及文件記錄，最終大家取得共識之後，在一次次的開發迭代之後，逐漸開發出產品。&lt;/p&gt;
&lt;p&gt;所以軟體開發流程裡面很重要的是團隊的&lt;strong&gt;共識&lt;/strong&gt;以及&lt;strong&gt;脈絡&lt;/strong&gt;，當我們只靠著幾句話就期待 AI 可以透過他所學習到的軟體工程師常識做出我們心目中理想的系統，缺乏的就是傳遞最重要的共識與脈絡給它。&lt;/p&gt;
&lt;p&gt;因為它不是你的蛔蟲，不會從短短的幾句對話知道這些脈絡。&lt;/p&gt;
&lt;p&gt;團隊之間可以透過開會以及文件來凝聚共識與梳理脈絡，那我們怎麼樣給 AI 這樣的資訊呢？有幾件事情可以嘗試看看。&lt;/p&gt;
&lt;h2 id=&quot;用規則-rule-定義方向&quot;&gt;用規則 (Rule) 定義方向&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;rule-for-right-track.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1904&quot; height=&quot;1241&quot; src=&quot;/_astro/rule-for-right-track.Ccz50Og7_Z11rC5v.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;在 Cursor 裡面可以定義規則，雖然說要寫優質的規則還是需要花很多時間推敲與改善，但規則還是可以為 AI 訂定一個大致上的方向，讓行事作風可以更接近團隊。&lt;/p&gt;
&lt;p&gt;比如說要不要寫測試？要寫到什麼程度？偏好怎麼樣的 git commit message？元件的撰寫慣例、命名規則與採用的技術堆疊是哪些？這些沒有交代時，他經常就會隨心所欲，就像是一個剛加入團隊還沒適應開發文化的軟體工程師。&lt;/p&gt;
&lt;h2 id=&quot;切分規格-spec逐步前進&quot;&gt;切分規格 (Spec)，逐步前進&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;rules-specs-impl.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1904&quot; height=&quot;1241&quot; src=&quot;/_astro/rules-specs-impl.DIlftk9A_6AljF.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;實作前先寫一份功能規格（當然是請他寫），並且先充分的閱讀與討論規格，確保他想做的事情跟你想做的事情一樣，確定之後再請他實作。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;rule-spec-workflow.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1904&quot; height=&quot;1241&quot; src=&quot;/_astro/rule-spec-workflow.D4vnUEhT_ZpHewc.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;至於功能規格不用全部都自己寫，我們的狀況是通常會先把任務在專案管理服務（比如說 Asana）先開出來之後，我會先把我所知道這個任務要做什麼告訴它，並且請它寫出規格，然後就它展開的這份規格進行討論與更新，完成規格之後再開一個新的 Chat Context 請他按照這份規格實作。&lt;/p&gt;
&lt;p&gt;如果覺得規格寫得不好，那就修訂產生 spec 的 rule，讓團隊之後要生成 spec 都可以有更好的文件品質。&lt;/p&gt;
&lt;p&gt;至於規格的篇幅長短會依照團隊習慣有所不同，但是小一點的話閱讀起來比較容易，同時也比較可以按照我們的意思開發，這也有助於我們更好的達成目標。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;feature-specs.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1904&quot; height=&quot;1241&quot; src=&quot;/_astro/feature-specs.C9NK3AYu_Z1e9vYV.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;前面有提到沒有脈絡或共識，會很難讓它做出我們想要的產品。除了透過 Rule 建立脈絡與共識外，另外避免這個問題的方法是訂出目標之後，先不要一次把所有規格寫出來，而是只寫你正要完成的那個功能的規格。&lt;/p&gt;
&lt;p&gt;由於我們會根據它寫的 spec 來矯正它想前進的方向，所以在每一次撰寫與實作一份 spec 時都能修正偏差，這樣就能讓專案從想法往實際產品的路上可以朝著預期的方向。&lt;/p&gt;
&lt;h2 id=&quot;規格最好有驗收條件&quot;&gt;規格最好有驗收條件&lt;/h2&gt;
&lt;p&gt;規格的內容會跟著每個團隊的不同而有差異，但會建議有驗收條件 (Acceptance Criteria)，這個驗收條件是用來明確的告訴 AI 怎麼樣才是完成了，這樣明確的條件可以讓 AI 更具體地知道它到底要完成到什麼程度。&lt;/p&gt;
&lt;p&gt;而驗收條件有很多方式，從工程師的角度來說可以用自動化的測試或是檢驗來代替，比如說單元測試或是整合測試。另外如果是在進行網頁應用程式的開發，可以用 &lt;a href=&quot;https://github.com/microsoft/playwright-mcp&quot;&gt;microsoft/playwright-mcp&lt;/a&gt; 跟 AI 說直接打開瀏覽器看目前的結果是什麼，讓他直接開啟網頁操作來驗證。&lt;/p&gt;
&lt;p&gt;當它可以更好的判斷目前的成果時，就更容易地可以判斷完成度並且採取後續行動。&lt;/p&gt;
&lt;p&gt;如果沒辦法自動化檢測，那也可以請他列出手動測試的方式，由開發者自行驗證，然後再跟他說結果。不過當然還是可以由他自行檢驗、自行修正會更好。&lt;/p&gt;
&lt;h2 id=&quot;到目前為止&quot;&gt;到目前為止…&lt;/h2&gt;
&lt;p&gt;因為我們也還在嘗試這樣的開發模式，在這樣的過程中也很有多需要調整的地方。目前覺得從 rules -&gt; spec -&gt; implementation 這樣的工作流程運作起來還行，寫 spec 時就有機會跟 AI 討論以及更新計畫，這樣就可以在開始動工前，先調整成我們想要完成的樣子。&lt;/p&gt;
&lt;p&gt;但我們也遇到了 Cursor 不太擅長遵守規則的問題。雖然說隨著時間這些問題應該會慢慢地被修正，或是累積出更好的實踐方式。不過在那天到達前我們大概都要經常得修改規則，目前確實覺得規則寫太長就會開始忘東忘西，明確並且短一點會更好，另外規則的敘述也很重要，因為會影響 AI 什麼時候會想要套用特定規則。&lt;/p&gt;
&lt;p&gt;另外，最近也滿多人談論 Vibe Coding，也就是用近乎直覺的方式用對話進行開發，不太管實作細節。&lt;/p&gt;
&lt;p&gt;但其實所謂的「直覺」很大一部份都是你在一個領域已經有了深厚的背景知識與經驗，所以才能讓你看起來毫不費力地的用「直覺」完成，實際上並不是每個人都可以做到，更何況在還牽涉到表達能力。&lt;/p&gt;
&lt;p&gt;要達到這個程度，還是得看操作的人對於這個產品的領域是否有足夠深入的看法，以及具備足夠細緻清晰的表達能力。&lt;/p&gt;
&lt;p&gt;我會覺得每個人應該都從自己的角度切入，看自己適合用怎麼樣的方式跟 AI 一起協作開發軟體專案。身為軟體工程師與一個好奇的人，再加上自己在經年累月的寫作有累積了一定的表達能力，就會採取適合我的方法跟 AI 協作，更精準的描述自己的需求、切分工作、設定驗收條件與軟體開發偏好，甚至用自動化測試的方式來讓 AI 可以做得更好。&lt;/p&gt;
&lt;p&gt;我只是從我的角度出發，了解自己喜歡與擅長什麼，然後訂製了跟 AI 的工作流程，而也在這個互動時發現到了比起寫程式，我更喜歡打造產品。與 AI 互動時才有機會把這兩件事情拆得更開，回過頭來理解自己。&lt;/p&gt;
&lt;p&gt;而每個人都不同，我的建議是回過頭審視自己喜歡什麼、擅長什麼，再找到一個適合的方式跟 AI 協作，理解自己這件事情是沒有捷徑的，每個人就是要花很多時間探索。&lt;/p&gt;
&lt;p&gt;甚至如果你的興趣就是自己撰寫程式，這個過程讓你感到快樂的話，那或許不用 AI 對你才是最好的。&lt;a href=&quot;https://www.youtube.com/watch?v=pVr3sEeus6E&amp;#x26;t=1245s&quot;&gt;浦澤直樹的專訪&lt;/a&gt;中提到了對 AI 繪圖的看法，他說：「因為我覺得繪畫是很快樂的事情，像我可以在工作中找到樂趣的人來說，交給 AI 做不就太可惜了嗎？」。&lt;/p&gt;
&lt;p&gt;大眾都在追求的東西，不見得就是適合你，還是得回過頭來理解自己是怎樣的人，對什麼事情充滿熱情，用屬於自己的觀點邁出下一步。&lt;/p&gt;
&lt;p&gt;AI 聽不懂人話沒關係，你嘗試著理解你自己更重要。&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>在 Claude Desktop 內直接進行代幣買賣</title><link>https://yurenju.blog/zh/posts/2025-03-13_uniswap-mcp/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2025-03-13_uniswap-mcp/</guid><pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;對話型 AI 工具最早都無法存取任何外部資訊，後來才逐漸地加入外部工具如搜尋功能。但「搜尋」這樣的功能又大又廣，針對一個特殊的功能比如說看天氣、查股票價格等等雖然搜尋網路資料也做得到，但還是比不上直接透過 API 整合一個新的天氣或是股市功能。&lt;/p&gt;
&lt;p&gt;回過頭來看網路上的服務成千上萬，不可能一一的透過 API 接上。更何況有些工具並不是 HTTP API 形式，而是本地電腦才有的工具。比如說寫程式的時候，希望編輯器可以存取到瀏覽器的精確畫面、結構以及開發者除錯資訊，這些就無法透過 HTTP API 的形式接上。&lt;/p&gt;
&lt;p&gt;而 MCP 就是一套開放標準，用來讓 AI 理解要怎麼樣去取用一個工具。比如說希望它幫忙管理待辦事項，就使用一組 Asana 的 MCP 透過 API 連接上待辦事項服務，這樣就可以讓它協助規畫整個專案以及撰寫所需要的資料，甚至管理任務的相依性等等。&lt;/p&gt;
&lt;p&gt;前面舉例的瀏覽器協助開發，也可以透過 &lt;a href=&quot;https://github.com/executeautomation/mcp-playwright&quot;&gt;mcp-playwright&lt;/a&gt; 來操作瀏覽器以及直接閱讀 console 判斷錯誤以及自動修正。&lt;/p&gt;
&lt;p&gt;剛好工作上有個相關的任務需要研究相關的事情，就嘗試透過 Protocolink, Moralis 寫了一個 Uniswap 的 MCP，讓它可以直接在 Claude Desktop 進行交易。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;uniswap-mcp.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1704&quot; height=&quot;1344&quot; src=&quot;/_astro/uniswap-mcp.CdheqHGK_18vyDW.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;可以到 &lt;a href=&quot;https://www.youtube.com/watch?v=7fRmwQYaBLg&quot;&gt;這個 Youtube 連結&lt;/a&gt; 看展示。&lt;/p&gt;
&lt;p&gt;做完之後又讓我想到了這近十年來傳訊軟體的發展。在最早以前是 LINE 跟 Telegram 的 mini app 開始流行推廣，不過後來大多都導到外部網頁實作大部分功能，僅有小部分簡易的 UI 會內嵌在傳訊對話裡面。如果是我也會這麼做，畢竟在外部網站實作還是比較簡單，只要在最必要的使用者驗證在 LINE 裡面處理就好。&lt;/p&gt;
&lt;p&gt;而前幾個月看到 Vercel 的  &lt;a href=&quot;https://vercel.com/blog/ai-sdk-3-generative-ui&quot;&gt;AI SDK 3.0&lt;/a&gt; 是我意識到未來軟體使用者介面可能會很不一樣的時刻。他的公告裡面包含了一個工具可以詢問天氣之後，直接產生一個天氣的顯示介面。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;vercel-ai-sdk.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1653&quot; height=&quot;757&quot; src=&quot;/_astro/vercel-ai-sdk.B-R26kZc_1WPIDt.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;我原本以為是完全動態產生 UI，後來仔細看了一下文件原來還是要&lt;a href=&quot;https://sdk.vercel.ai/docs/ai-sdk-ui/generative-user-interfaces#create-ui-components&quot;&gt;預先定義 UI&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;不過即使他們還沒做到，但是也開啟了一個想像空間：假如說未來 UI 元件可以根據收到資料的脈絡，動態的組合出適合的 UI 介面，那會是怎麼樣的體驗？如果往後的使用者互動跟現在完全不一樣呢？會是語音對談之後，直接完全動態的產生合適的使用者介面嗎？&lt;/p&gt;
&lt;p&gt;那在你我所在的產業，又會產生怎麼樣的影響？&lt;/p&gt;
&lt;p&gt;我覺得嘗試著去投射或是想像未來是件有趣的事情，未來看起來是混沌又有趣的，但我希望有趣的成分多一點。&lt;/p&gt;
&lt;h2 id=&quot;後記&quot;&gt;後記&lt;/h2&gt;
&lt;p&gt;這個 uniswap-mcp 只是為了研究目的而撰寫，程式碼 99% 全部都是由 Cursor 寫的。你我都知道，我們不該把私鑰透過環境變數傳入程式，但如果還是很好奇的話，以下是它的源碼，請只拿來做測試與研究，還有不要放太多錢進去。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yurenju/uniswap-mcp&quot;&gt;https://github.com/yurenju/uniswap-mcp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>技術</category></item><item><title>寫在 Perplexity Comet 瀏覽器釋出之前</title><link>https://yurenju.blog/zh/posts/2025-02-25_before-perplexity-comet/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2025-02-25_before-perplexity-comet/</guid><pubDate>Tue, 25 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;perplexity-comet-browser.jpg&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1966&quot; height=&quot;745&quot; src=&quot;/_astro/perplexity-comet-browser.BynsB0Tb_Z1LJI8J.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Perplexity 要推出新的瀏覽器 Comet 了，雖然不知道它實際上會推出怎樣的瀏覽器，但是想像一下是滿有趣的。假設這個瀏覽器是人類跟 AI 協作一起上網，比如說你想要找什麼資料，他會幫你到各個搜尋引擎搜索跟閱讀整理資料。你需要改 Google Docs 或是 Notion，他可以直接幫你打開網頁更新文件。如果是這樣的話我覺得改變會滿大的。&lt;/p&gt;
&lt;p&gt;之前有聽過一個為什麼要做人形機器人的原因，是因為整個世界都是用人作為參考去設計的，樓梯、門把、洗衣機、電視遙控器等等。一個新的造物如果是別種型態，就沒辦法操作這些累積的幾百幾千年的基礎建設。&lt;/p&gt;
&lt;p&gt;我覺得這個瀏覽器感覺有點類似這樣。以往的網路服務如果想要讓機器存取，就需要開新的 API 才作得到。甚至面對各種自動化爬蟲還設置了真人偵測器，比如說像是 reCAPTCHA。&lt;/p&gt;
&lt;p&gt;但如果接下來的瀏覽器，是人跟 AI 協作上網時，一下子許多基礎建設都可以使用了，自動化操作再也沒有了藩籬，需要人接手操作時，AI 會請求你幫忙一下，通過之後他再繼續工作。這樣想像起來的未來世界是很不一樣的，很多以前覺得要跨網頁服務的整合，一下子都變得簡單了起來 — 同時我們還要解決很多新問題 🤣&lt;/p&gt;
&lt;p&gt;未來果然還是混沌又有趣的，我希望有趣的成分比混沌多一點。&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>ChatGPT 是好奇心的反射與延伸</title><link>https://yurenju.blog/zh/posts/2024-04-29_chatgpt-curiosity-reflection/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2024-04-29_chatgpt-curiosity-reflection/</guid><pubDate>Mon, 29 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;封面照片&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1000&quot; height=&quot;1000&quot; src=&quot;/_astro/cover_chatgpt-curiosity-reflection.UmRChsBz_Z1NS59j.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;原創性的根源是好奇心，就像是問題與答案的關係一樣，既然一個好的&lt;strong&gt;提問&lt;/strong&gt;本身就是&lt;strong&gt;答案&lt;/strong&gt;的重要組成，這也代表好奇心也是一種原創的力量。 — Paul Graham&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot; id=&quot;user-content-fnref-1&quot; data-footnote-ref=&quot;&quot; aria-describedby=&quot;footnote-label&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;最近我大量使用 ChatGPT 作為日常使用，不論是自己文章的校稿、寫筆記時讓它提出反面觀點，各式各樣讓我感到疑惑的問題都會使用 ChatGPT 進一步的理解，當然也少不了懷疑它的答案。隨著越常使用它，我愈來愈覺得他是一個某種投射。它就像是一個博學多聞但偶爾細節會出錯的對答機器。但你不說話的時候，它是不會主動解決你的疑惑。&lt;/p&gt;
&lt;p&gt;只有當你提問的時候，這個對答機器才會有條不紊的整理出它的回答。&lt;/p&gt;
&lt;p&gt;《&lt;a href=&quot;https://www.youtube.com/watch?v=pVr3sEeus6E&amp;#x26;t=1400s&quot;&gt;浦沢直樹 × 米山舞 スペシャルお絵描き対談&lt;/a&gt;》 的專訪中浦沢直樹提到了對於 AI 繪圖工具的看法，其中一段他提到如果在動畫中要繪製無比壯闊的夕陽場景時，要用手繪會需要花費非常多的時間，這個時候 AI 繪圖就可以幫上忙。但是在這之前，自己的腦中要已經有對這個壯闊的場景已經有了自己的想像，才有辦法後續透過 AI 繪圖來輔助。&lt;/p&gt;
&lt;p&gt;「如果腦袋裡沒有想法，那也是行不通的。」浦沢直樹說。&lt;/p&gt;
&lt;p&gt;回過頭來說，我平常使用 ChatGPT 的時候也會為它設定一個角色，比如說拿來校訂筆記用的角色是「你是一個嚴格的編輯人員，會對我的筆記提出反面的觀點或是批評，並且建議如何修改筆記內容」。但當你為他設定一個角色時，你就是將你的所需要的功能與觀點加諸在它身上了。所以你是在跟自己的延伸角色對話，只是它擁有更大量的知識。但你為它設置了他的觀點、角色以及它要作的事情，而這些設置才是真的賦予原創性的地方。&lt;/p&gt;
&lt;p&gt;作為一個充滿好奇心的人，不停地對它提出各式各樣的問題。面對難題時，又跟自己所建立的延伸角色對話，讓自己在更迭循環之中愈來愈理解自己的看法。表面上使用 ChatGPT 是想要尋求解答，但背後的力量泉源是好奇心與提問。&lt;/p&gt;
&lt;p&gt;對我來說 ChatGPT 是好奇心的投射與延伸。最終，重要的還是背後那個提問的人。&lt;/p&gt;
&lt;section data-footnotes=&quot;&quot; class=&quot;footnotes&quot;&gt;&lt;h2 class=&quot;sr-only&quot; id=&quot;footnote-label&quot;&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id=&quot;user-content-fn-1&quot;&gt;
&lt;p&gt;引用自周欽華翻譯的《&lt;a href=&quot;https://www.facebook.com/chouchinhua/posts/pfbid03cpFHNMvCMmDmkv682Ec283EJrpmwqErZZFTKBprmaj4PU5QuZPTdWoo3karioszl&quot;&gt;如何做出偉大的成就&lt;/a&gt;》Facebook 貼文，我再進行些許文字調整 &lt;a href=&quot;#user-content-fnref-1&quot; data-footnote-backref=&quot;&quot; aria-label=&quot;Back to reference 1&quot; class=&quot;data-footnote-backref&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded><category>技術</category></item><item><title>tw-did 補充資訊</title><link>https://yurenju.blog/zh/posts/2024-02-08_tw-did-additional-info/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2024-02-08_tw-did-additional-info/</guid><pubDate>Thu, 08 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;上篇文章有提到這個專案的合作架構有點複雜，每次有人問我都要花很多時間解釋，寫在這裡讓以後我可以請大家直接看這篇短文。&lt;/p&gt;
&lt;p&gt;這個專案的源頭其實是數位發展部發包的一個標案，並且由開拓文教基金會得標，而開拓文教基金內負責這個標案的參與成員大多來自於 DA0 社群。這個標案其實包含了許多專案，其中之一就是串接 W3C DIDs 與行動自然人憑證的驗證專案，也就是我參與的這個開源專案，但是&lt;strong&gt;我並沒有參與標案&lt;/strong&gt;，這就是複雜的地方 😎&lt;/p&gt;
&lt;p&gt;前面有提到我是受到 Ethereum Foundation 的邀請，接受了基金會的 Grant Program，這是因為基金會裡面有些熱心人士認為這樣&lt;strong&gt;去中心化身分識別&lt;/strong&gt;與政府機關之間的整合機會是很罕見的，希望可以順利的透過開源並且符合公眾利益的方式達成。所以經過討論之後，基金會為這個專案提供的&lt;strong&gt;技術支援&lt;/strong&gt;，而這個技術支援的方式就是透過 Grant Program 找一個合適參與這個專案的人來負責規劃與實作這個專案，而我就是因此參與這個專案。&lt;/p&gt;
&lt;p&gt;所以在這個技術支援的框架下，我接受了基金會的 Grant Program 在這個專案提供技術支援，包含設計、規劃與開發專案，最終將源碼交給開拓文教基金會去完成這個標案。而我並不是獨自開發這個專案，還有其他幾個開發者也參與了前端開發、佈署等等工作，詳情請見 &lt;a href=&quot;https://github.com/moda-gov-tw/tw-did?tab=readme-ov-file#contributors&quot;&gt;README 的 contributors 一節&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;所以從我的角度來看，自己參與了一個開放源碼的專案，並且由 Ethereum 基金會提供獎助金。而我跟開拓文教基金會沒有合約關係，也沒有參與標案，僅是作為 Ethereum 基金會的一個獎助計畫參與者，對此計畫提供技術支援並且開發了一個開源專案。&lt;/p&gt;
&lt;p&gt;這個獎助計畫在十二月底結束，而結束後在一月中旬時，數位發展部的豆泥問我有沒有興趣為接下來的&lt;strong&gt;數位皮夾草案&lt;/strong&gt;提供為期半個月的技術可行性評估，在這個時候我才作為顧問的角色參與數位發展部的草案技術評估，此顧問工作也已經於一月底結束。&lt;/p&gt;
&lt;p&gt;所以如果你聽到的資訊是 DA0 的 Noah 負責了此專案，那是正確的，Noah 在這個專案主要負責了大多數的協調工作、宣傳與交流活動。如果你又聽到 Ethereum 基金會贊助了這次開發工作，那也是對的，因為我作為技術支援參與了這個專案的規劃與開發。&lt;/p&gt;
&lt;p&gt;希望這樣的解釋可以讓大家理解這個複雜的合作關係 🤣&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>概念驗證：隱私優先的台灣居民數位身分</title><link>https://yurenju.blog/zh/posts/2024-02-04_taiwan-digital-id-privacy-first/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2024-02-04_taiwan-digital-id-privacy-first/</guid><pubDate>Sun, 04 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在 W3C DIDs 系列文章裡面，從首篇《&lt;a href=&quot;https://yurenju.blog/posts/2023-08-21_fb-ban-and-did-solution/&quot;&gt;從 Facebook 無端封鎖帳號來看數位身分的問題與 DID 解決方案&lt;/a&gt;》提出了現今常用的數位身分掌控在大型企業如 Google 或是 Facebook 手裡，他們可以刪除你的數位身分而不需要你的允許或同意；第二篇《&lt;a href=&quot;https://yurenju.blog/posts/2024-01-01_w3c-dids-redefining-identity-authority/&quot;&gt;W3C DIDs：拆解權力結構的數位身分標準&lt;/a&gt;》從 W3C DIDs 標準出發，更深入了探討透過 DIDs 與 VC 標準要怎麼樣解決數位身分的自主權與隱私權的問題；到第三篇《&lt;a href=&quot;https://yurenju.blog/posts/2024-02-02_semaphore/&quot;&gt;Semaphore：強化隱私的身分解決方案&lt;/a&gt;》更深入的理解前沿科技如 Semaphore 如何提供&lt;strong&gt;匿名但可驗證&lt;/strong&gt;的開發套件。&lt;/p&gt;
&lt;p&gt;而本篇文章將會融合以上資訊，來講解去年下半年我所開發的專案。&lt;/p&gt;
&lt;p&gt;感謝 Ethereum Foundation 的 Phini 的邀請，我在 2023 下半年接受了基金會的 Grant Program，參與一個多方合作計畫並且開發一套整合台灣行動自然人憑證、W3C DIDs/VC 與 Semaphore 零知識證明框架的實驗性專案 &lt;a href=&quot;https://github.com/moda-gov-tw/tw-did&quot;&gt;tw-did&lt;/a&gt;，改善集中式數位身分解決方案目前在自主權與隱私權的問題。&lt;/p&gt;
&lt;p&gt;此專案目前已經轉移給數位發展部，這個專案的合作結構有點複雜，我會在後續的&lt;a href=&quot;https://yurenju.blog/posts/2024-02-08_tw-did-additional-info/&quot;&gt;短文&lt;/a&gt;補充細節。&lt;/p&gt;
&lt;p&gt;首先先介紹一下之前都還沒提到的&lt;strong&gt;行動自然人憑證&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&quot;行動自然人憑證&quot;&gt;行動自然人憑證&lt;/h2&gt;
&lt;p&gt;在台灣報稅時，最方便的方式是直接採用線上報稅。而其中一種驗證身分方式就是&lt;strong&gt;自然人憑證&lt;/strong&gt;。自然人憑證是一張晶片卡，裡面有一把私鑰儲存在安全晶片當中，並且可以透過晶片讀卡機來簽名與驗證，作為一個台灣居民的數位憑證。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;自然人憑證插卡&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1998&quot; height=&quot;1520&quot; src=&quot;/_astro/moica.KJjQ9Pom_ZA9Nf9.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;尚未有擁有卡片的民眾會需要透過戶政事務所現場臨櫃申請，公所發行實體卡片後，就可以用這張晶片卡到其他公務機關使用，比如說線上報稅服務。&lt;/p&gt;
&lt;p&gt;自然人憑證雖然安全，但是實體卡片的缺點就是會需要使用晶片讀卡機才可以使用，即使這張晶片卡也支援 NFC 感應讀卡，但在大多數的場合都需要使用插卡式讀卡機，除了在電腦上使用還要額外的讀卡機以外，在手機上也難以使用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;行動自然人憑證&lt;/strong&gt;就是為了解決這樣不便的問題。&lt;strong&gt;行動自然人憑證&lt;/strong&gt;是一個&lt;strong&gt;自然人憑證&lt;/strong&gt;的延伸解決方案，下載行動自然人憑證 app 之後，Android 裝置可以透過 NFC 的方式感應讀取到自然人憑證卡片的資訊，並且將這張實體卡片跟手機綁定，未來就可以直接用手機 app 作為自然人憑證卡片使用。&lt;/p&gt;
&lt;p&gt;驗證時將會透過生物識別如指紋或是人臉辨識保護，解鎖之後用安全硬體裡面的私鑰進行簽名與身分驗證。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;行動自然人憑證的綁定與驗證&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1998&quot; height=&quot;1126&quot; src=&quot;/_astro/twfido.CC61zhp2_1raSrd.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;行動自然人憑證解決了實體晶片卡的不便問題，透過手機就可以驗證自己的台灣居民身分。如果以便利性為目標來說，確實解決了一大問題。另外使用生物驗證裝置、無須記憶密碼，並且採用手機的安全區域存放私鑰，安全性也足夠。搭配上沒有離開手機的指紋或是臉部辨識資料，整體上算是個面面俱到的解決方案。&lt;/p&gt;
&lt;p&gt;當然行動自然人憑證還是一種集中式的身分驗證方案，我們同樣可以用在之前文章提到的兩個現行身分驗證方案的主要問題&lt;strong&gt;自主性&lt;/strong&gt;與&lt;strong&gt;隱私性&lt;/strong&gt;來觀察該解決方案。&lt;/p&gt;
&lt;p&gt;在這之前要先說明一下當使用者要到一個機關使用行動自然人憑證進行台灣居民身分的驗證時，將會使用內政部的 API 進行驗證程序。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;行動自然人憑證依靠內政部的 API&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1906&quot; height=&quot;1144&quot; src=&quot;/_astro/twfido-issues.CRiaMKmH_ZqCEsG.webp&quot; &gt;&lt;/p&gt;
&lt;h3 id=&quot;自主性&quot;&gt;自主性&lt;/h3&gt;
&lt;p&gt;行動自然人憑證雖然透過非對稱式密碼學進行身分驗證，但該服務的發行方同時也參與了驗證程序，這代表如果政府想要封鎖特定台灣居民時，可以在驗證程序呼叫 API 時，透過黑名單來拒絕提供驗證服務。&lt;/p&gt;
&lt;p&gt;舉例極端的例子來說，一間國外的信用卡公司支援台灣居民申請信用卡，並且整合了行動自然人憑證作為驗證方式。假設台灣未來變成極權國家了，並且把所有以前發出去的台灣居民憑證全部撤銷，即使這間國外的信用卡公司願意接受原本的台灣居民申請，政府還是可以輕易的透過黑名單讓一個政府不喜歡的人無法通過驗證。&lt;/p&gt;
&lt;h3 id=&quot;隱私性&quot;&gt;隱私性&lt;/h3&gt;
&lt;p&gt;這跟自主性問題的源頭是一樣，因為發行方參與了驗證過程，代表每次使用者進行驗證時，政府都會知道。這以前並不是個大問題，因為行動自然人憑證只開放給政府機關進行驗證程序，大眾還可以接受政府機關追蹤同樣是政府機關的登入行為。&lt;/p&gt;
&lt;p&gt;但是在 2023 下半年之後根據&lt;a href=&quot;https://fido.moi.gov.tw/pt/main/news_detail/16&quot;&gt;公告&lt;/a&gt;，行動自然人憑證的適用範圍擴大至&lt;strong&gt;非公務機關&lt;/strong&gt;，這時候隱私問題就需要更慎重的考慮，想像未來有一個服務如網路銀行登入的時候要透過行動自然人憑證服務來驗證身分時，政府可以紀錄每一個使用者進行登入行為時的數位足跡，這樣就把數位足跡的蒐集擴大到民間企業了。&lt;/p&gt;
&lt;p&gt;上面的論點好像把政府想像的很壞，其實並不是這樣的。區塊勢的 podcast 的&lt;a href=&quot;https://blocktrend.substack.com/p/ep235&quot;&gt;其中一集許明恩跟豆泥的訪談&lt;/a&gt;裡面也提到新科技如果不經過適當的引導，科技不見得會走向普世認同的價值觀，因為政府擁有的權力很大，如果不適度的導引科技的走向，很容易就會走偏了方向。&lt;/p&gt;
&lt;p&gt;如果在我們有機會制定機制與框架時，應該盡早的把科技框架設計的往好的方向前進。既然可以設計成&lt;strong&gt;自主與隱私優先&lt;/strong&gt;，我們應該就該朝著這個方向前進。&lt;/p&gt;
&lt;h2 id=&quot;tw-did-實驗專案&quot;&gt;TW-DID 實驗專案&lt;/h2&gt;
&lt;p&gt;講解完行動自然人憑證終於可以開始介紹這個專案了。&lt;a href=&quot;https://github.com/moda-gov-tw/tw-did&quot;&gt;tw-did&lt;/a&gt; 是一個實驗性質的專案，整合了行動自然人憑證、W3C DIDs/VC 標準與 Semaphore 零知識證明框架，並且從專案的產出來窺探未來的模樣，以及審視現在的不足之處。&lt;/p&gt;
&lt;p&gt;前面提到的行動自然人憑證仍存在的自主性以及隱私性問題，而 tw-did 可以透過整合行動自然人憑證，把台灣居民的資格憑證介接與轉發成 W3C VC 格式的憑證來解決這兩項問題，並且進一步的透過 Semaphore 將隱私性更往前推進，讓驗證程序時完全不需揭露數位身分。&lt;/p&gt;
&lt;p&gt;接下來我們會分成兩個部分講解：&lt;strong&gt;W3C DIDs/VC 的整合&lt;/strong&gt;與 &lt;strong&gt;Semaphore 的整合&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&quot;w3c-didsvc-整合&quot;&gt;W3C DIDs/VC 整合&lt;/h3&gt;
&lt;p&gt;tw-did 提供了一個網頁服務，此服務首先透過行動自然人憑證來登入、驗證使用者的台灣居民身分，接著驗證使用者的 DID 身分識別。這兩項資訊都驗證成功後，就會發出一張&lt;strong&gt;可驗證憑證&lt;/strong&gt;（Verifiable Credential, VC）格式的憑證給這位使用者，讓使用者可以在別的需要證明他是台灣居民的網站使用這張憑證。&lt;/p&gt;
&lt;p&gt;就如同《&lt;a href=&quot;https://yurenju.blog/posts/2024-01-01_w3c-dids-redefining-identity-authority/&quot;&gt;W3C DIDs：拆解權力結構的數位身分標準&lt;/a&gt;》裡面介紹的一樣，不管是&lt;strong&gt;發行方&lt;/strong&gt;或是&lt;strong&gt;持有者&lt;/strong&gt;都可以採用不同的 DID Method，不過為了讓實驗環境單純一點，tw-did 專案裡面一律都採用了以太坊區塊鏈的 DID Method &lt;code&gt;did:ethr&lt;/code&gt; 與以太坊帳號作為 DID 身分識別，而驗證 DID 身分時將會使用以太坊的常見驗證方式 Sign-In with Ethereum (SIWE) 透過 MetaMask 的訊息簽章驗證使用者確實擁有該帳號，也就證明了他擁有該 DID 的私鑰。&lt;/p&gt;
&lt;p&gt;當透過行動自然人服務確認持有者是台灣居民以及擁有該 DID 識別後，tw-did 作為發行方就可以發行 VC 格式的憑證給使用者了。發行時 tw-did 將會使用發行者 DID 簽署該憑證，並且提供給持有者下載收藏憑證。整體的發行流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;行動自然人憑證整合 W3C DIDs/VC 的發行流程&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1906&quot; height=&quot;1144&quot; src=&quot;/_astro/twfido-w3c-dids-vc-issuance.Dfu-eV_G_Z1c8seE.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;發行流程的步驟二依然有呼叫內政部 API 並且提供使用者身分證字號，但是這是唯一一次會揭露持有者身分的 API 呼叫，而在驗證流程並不會揭露是哪個持有者正在進行驗證。&lt;/p&gt;
&lt;p&gt;假設現在有一個銀行支援了 W3C DIDs/VC 標準的驗證流程，使用者就可以出示這張憑證來證明他自己確實是台灣公民，流程圖如下。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;行動自然人憑證結合 W3C DIDs/VC 的驗證流程&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2658&quot; height=&quot;1590&quot; src=&quot;/_astro/cover_twfido-w3c-dids-vc-verification.Bgg5zXuP_ySKtn.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;當驗證機關如銀行請求使用者提供特定的 VC 格式憑證時會一併提出一個密碼學挑戰。使用者收到這個請求時，會使用自己 DID 所對應的私鑰針對密碼學挑戰進行回覆，並且把原始的 VC 憑證、挑戰以及挑戰回覆（也就是簽名）一併包裝成&lt;strong&gt;可驗證展示文件&lt;/strong&gt;（Verifiable Presentation, VP）回覆給銀行。&lt;/p&gt;
&lt;p&gt;銀行接下來會檢查 VP 格式的憑證的各項內容是否正確，另外還會額外跟發行方索取撤銷清單。因為不是透過 API 詢問&lt;strong&gt;特定憑證&lt;/strong&gt;是否有撤銷而是一次取得整個撤銷清單，讓驗證者自己在從清單裡面找到該憑證的撤銷資訊。這樣就可以保持發行者不知道現在是哪個持有者正在進行驗證，但還是可以得知是否撤銷。&lt;/p&gt;
&lt;p&gt;最後內政部的 API 並不會被呼叫，這也代表內政部並不會參與驗證流程。&lt;/p&gt;
&lt;p&gt;當驗證成功，銀行確認使用者為台灣居民後，就可以開始使用銀行的服務。同樣不免俗的來驗證這個架構下的數位身分自主性與隱私性。&lt;/p&gt;
&lt;h4 id=&quot;自主性-1&quot;&gt;自主性&lt;/h4&gt;
&lt;p&gt;銀行透過 VP 格式的憑證來驗證使用者的身分時，VP 文件裡面的原始 VC 憑證裡面已經包含了發行者的簽名，銀行可以直接利用公開資訊驗證發行者的簽名，不需要發行者的參與，所以也無法阻擋驗證流程。&lt;/p&gt;
&lt;p&gt;雖然發行者可以把憑證標記成撤銷，但即使如此這個憑證的發行者簽章還是有效的，因為簽章的結果是&lt;strong&gt;不可否認&lt;/strong&gt;的。這代表即使憑證被撤銷了，驗證者還是可以自行決定要不要採用這個情況的憑證。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;vc 的各種狀態是分開驗證&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1352&quot; height=&quot;802&quot; src=&quot;/_astro/vc-autonomy.Z17fP81E_Z1eA3np.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;就如同上圖的狀態，VC 的各種狀態是分開驗證的。驗證期限並不會影響到驗證發行者簽名，憑證被撤銷了也不會影響到驗證發行者簽名。&lt;/p&gt;
&lt;p&gt;舉前面的極端例子來說，假設台灣未來變成了極權國家，並且把原本的居民身分全部都撤銷，這個時候驗證者收到使用者的被撤銷的憑證時，還是可以考慮是否要採用這樣的憑證作為證明，而不是把所有權力都放在發行者身上。&lt;/p&gt;
&lt;p&gt;相較於發行方會參與整個驗證流程相比較起來，W3C DIDs/VC 規範的流程還是擁有比較高的數位身分自主性。&lt;/p&gt;
&lt;h4 id=&quot;隱私性-1&quot;&gt;隱私性&lt;/h4&gt;
&lt;p&gt;這同樣跟發行方有沒有參與驗證流程相關，使用者在銀行出示 VC 格式的憑證時，發行方並沒有辦法得知與追蹤，這也讓隱私性在驗證流程中得以保障。&lt;/p&gt;
&lt;p&gt;在常規的 W3C DIDs/VC 的架構下在自主性與隱私已經有保障了，接下來我們來看看 Semaphore 整合可以為我們帶來什麼樣的好處。&lt;/p&gt;
&lt;h3 id=&quot;semaphore-整合&quot;&gt;Semaphore 整合&lt;/h3&gt;
&lt;p&gt;Semaphore 整合相較於一般 W3C DIDs 標準驗證程序來說，可以做到即使&lt;strong&gt;不揭露 DID 識別&lt;/strong&gt;也可以達到&lt;strong&gt;驗證資格&lt;/strong&gt;的目的。&lt;/p&gt;
&lt;p&gt;以 tw-did 實驗專案的情況來說，常規的 VC 憑證內會紀錄持有者的 DID 識別字串（包含 Ethereum 帳號地址），裡面可以解析出公鑰資訊來驗證這個 VC 憑證是否正確。&lt;/p&gt;
&lt;p&gt;但這也代表使用者的 Ethereum 帳號地址會被揭露。&lt;/p&gt;
&lt;p&gt;而 Semaphore 整合則是可以完全不需要提供 DID 識別，也可以證明自己有台灣居民的資格。套用在行動自然人憑證的例子時，代表使用者不需要揭露他的 Ethereum 地址也可以驗證他的台灣居民身分。&lt;/p&gt;
&lt;p&gt;但我們在上一篇文章有提過 Semaphore 是一個開發套件，並不是一個像是 W3C DIDs 的標準，所以在 tw-did 實驗專案我們所進行的工作是將 Semaphre 嘗試整合到 W3C DIDs/VC 標準。比較好的作法是建立一個新的 DID Method 比如說 &lt;code&gt;did:semaphore&lt;/code&gt; 並且制定各種互動方式，但考量到時間以及實作的難易程度，我們決定先用 &lt;code&gt;did:web&lt;/code&gt; 來包裝 semaphore 驗證方式作為實驗的評估方案。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;行動自然人憑證整合 semaphore 框架&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2880&quot; height=&quot;1318&quot; src=&quot;/_astro/twfido-semaphore-issuance.B5W_mA9k_2ff9fV.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;在 Semaphore 整合的發行流程當中，使用者在透過行動自然人憑證服務確認自己的台灣居民身分後，接下來使用者會在客戶端產生 Semaphore Identity，這個身分識別由公開與秘密的部分所組成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;公開&lt;/strong&gt;：Commitment，功能類似非對稱式密碼學的公鑰部分&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;秘密&lt;/strong&gt;：Trapdoor 與 nullifier，功能類似非對稱式密碼學的私鑰部分&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而公開的 commitment 將會提供給 tw-did，它會被加入一個 Semaphore Group 裡面，作為具備台灣居民資格的證明。登記在 tw-did 的所有 commitments 清單任何人都可以取得，為公開資料。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;semaphore 驗證流程&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2652&quot; height=&quot;1740&quot; src=&quot;/_astro/twfido-semaphore-verification.DUfZOubw_Z23G8Vk.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;在 Semaphore 驗證過程當中，驗證方如銀行會發起一個密碼學挑戰，而只有具備台灣居民資格的 Semaphore Identity 才有辦法產生正確的挑戰回覆。&lt;/p&gt;
&lt;p&gt;這邊分成兩步驟：&lt;strong&gt;產生證明&lt;/strong&gt;與&lt;strong&gt;驗證&lt;/strong&gt;。產生證明會在持有者的客戶端執行，而驗證會由驗證方的服務進行。&lt;/p&gt;
&lt;p&gt;持有者在&lt;strong&gt;產生證明&lt;/strong&gt;時，會需要密碼學挑戰、自己的 Semaphore Identity 以及所有符合資格的 commitment 清單，其中的 commitment 清單要向 tw-did 索取。有了這三個輸入參數之後，就可以產生出相對應的證明了。&lt;/p&gt;
&lt;p&gt;而驗證者在&lt;strong&gt;驗證&lt;/strong&gt;時，則需要有兩個輸入參數，第一個是持有者提出的證明，第二個也是 commitments 清單，有了這兩個參數之後就可以驗證證明了。&lt;/p&gt;
&lt;p&gt;我們可以把一般的非對稱式密碼學與 Semaphore 零知識證明的證明驗證流程比較一下。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Semaphore 與常規的非對稱式密碼學的比較&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1804&quot; height=&quot;1658&quot; src=&quot;/_astro/pkc-semaphore-comparison.CjK2Y9qd_Z1uT7Gt.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;一般的非對稱式密碼學將會使用私鑰進行簽章，並且驗證者則會需要取得相對應的公鑰進行驗證。由於公鑰就代表了 DID 的鑰匙對，所以 DID 身分資訊還是會有一定程度的曝光。而在 Semaphore 的驗證流程並不需要提供代表特定身分所相對應的特定 commitment，而只需要所有符合資格的 commitments 清單即可，如此一來就可以在不揭露身分的前提下面進行驗證。&lt;/p&gt;
&lt;p&gt;這比起常規的 W3C DIDs/VC 來說在隱私性上又更加強了，驗證的時候也只能知道對方符合資格，但卻無法得知具體是誰。常規的 VC 憑證裡面一定都會包含公鑰資訊，像是如果 Ethereum 帳號不想要讓驗證方知道時，就可以透過這個更進階 Semaphore 零知識證明來達成強化的隱私保護。&lt;/p&gt;
&lt;p&gt;但這邊要注意的是 Semaphore 整合是非常實驗性質的整合，所以在開發上走了一些捷徑，讓現在這個雛形的數位身分自主性不像常規 VC 這麼好，但是這是明確知道要怎麼樣改善的問題，這在後面的&lt;strong&gt;回顧與探討&lt;/strong&gt;一節會討論。&lt;/p&gt;
&lt;h2 id=&quot;demo-影片&quot;&gt;DEMO 影片&lt;/h2&gt;
&lt;p&gt;這次做了一個嘗試是每一次里程碑釋出的時候都會附上一個展示影片，現在回頭看覺得很有趣，沒想到從八月到十月專案的變化可以這麼大 😎&lt;/p&gt;
&lt;p&gt;可以到這邊看一下每次里程碑的介紹以及影片： &lt;a href=&quot;https://github.com/moda-gov-tw/tw-did/releases&quot;&gt;https://github.com/moda-gov-tw/tw-did/releases&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;這邊引用 Milestone 6 的影片如下：&lt;/p&gt;
&lt;p&gt;{{&amp;#x3C; rawhtml &gt;}}
&lt;video width=&quot;100%&quot; controls&gt;
&lt;source src=&quot;tw-did-select-credentials.mp4&quot; type=&quot;video/mp4&quot;&gt;
Your browser does not support the video tag.
&lt;/video&gt;
{{&amp;#x3C; /rawhtml &gt;}}&lt;/p&gt;
&lt;p&gt;這邊有兩個分頁，Web 分頁是發行者，SampleVerifier 分頁是驗證者。&lt;/p&gt;
&lt;p&gt;在 Web 分頁當中會先透過行動自然人憑證服務驗證使用者的身分，在這個服務串接當中會透過使用者已經安裝的 &lt;strong&gt;行動自然人憑證 app&lt;/strong&gt; 送出通知，或者使用者也可以選擇掃描條碼一樣也可以進行登入程序。&lt;/p&gt;
&lt;p&gt;登入完成之後就會透過 Sign-In With Ethereum 以簽章的方式驗證使用者是否確實擁有一個 Ethereum 帳號，最後一個步驟則是產生一個 Semaphore 身分識別，並且將其 commitment 註冊到 tw-did 的資料庫當中。&lt;/p&gt;
&lt;p&gt;如此一來發行程序的前置作業就完成了。&lt;/p&gt;
&lt;p&gt;最後會讓使用者下載兩種不同的 VC 憑證，第一種是發給持有者的 Ethereum 帳號地址的 VC 憑證，另外一種則是發給持有者的 Semaphore Identity 的 VC 憑證，檔名分別是 &lt;code&gt;vc-ethereum.json&lt;/code&gt; 與 &lt;code&gt;vc-semaphore.json&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;接下來則是驗證程序的範例頁面 SampleVerifier。這個頁面作為開發與除錯的目的所以比較陽春一點。驗證網站可以驗證上面發出的兩種分別針對 Ethereum 以及 Semaphore 的憑證。&lt;/p&gt;
&lt;p&gt;在驗證 Ethereum 憑證的部分，首先會讓使用者選擇一個發給 Ethereum 地址作為 DID 的 VC 憑證，使用者可以直接用檔案選擇功能選擇 &lt;code&gt;vc-ethereum.json&lt;/code&gt; 進行驗證，或是按下 “Select on DID” 在發行網站選擇所需的 Ethereum 憑證。選擇之後會開始進行解析工作，當按下&lt;strong&gt;驗證&lt;/strong&gt;之後會透過 MetaMask 簽名、產生 VP 文件來確認持有者的資格。&lt;/p&gt;
&lt;p&gt;在驗證 Semaphore 憑證的部分，則是按下 &lt;strong&gt;Semaphore Challenge and verify&lt;/strong&gt; 會在驗證網站即時產生一個密碼學挑戰，而持有者將會在客戶端產生證明並且包裝成 VC 格式的憑證，回傳給驗證網站進行驗證程序。&lt;/p&gt;
&lt;h2 id=&quot;回顧與探討&quot;&gt;回顧與探討&lt;/h2&gt;
&lt;p&gt;從 Github 的提交紀錄來看，我在八月初提交了第一個 commit，然後到十月底專案完成。而在得知這個專案之前，我對 DID 的了解非常粗淺，所以不管是研究或是開發都是在十萬火急的狀況下完成。&lt;/p&gt;
&lt;p&gt;所以研究跟實作都難免有不足之處，很多都是邊學邊做，甚至有些知識是在專案結束之後我才知道怎麼樣才可以做得更好，寫在這邊給未來的開發人員作個參考。&lt;/p&gt;
&lt;p&gt;這個專案最重要的目標有幾個：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;這個雛形是否提供一個未來情境的想像，協助理解未來的正式環境我們可以做到哪些事情？&lt;/li&gt;
&lt;li&gt;這些機制整合在一起時，數位身分的自主性與隱私性是否有得到保障？&lt;/li&gt;
&lt;li&gt;開發的過程中是否有遇到問題可以供正式環境作為參考？&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;雛形是否提供未來場景的想像&quot;&gt;雛形是否提供未來場景的想像&lt;/h3&gt;
&lt;p&gt;在這個實驗當中整合的是行動自然人憑證，而發出去的憑證可以由另外一個驗證服務來確認這個使用者是否為台灣居民。&lt;/p&gt;
&lt;p&gt;我們可以想像未來這個台灣居民的憑證可以給許多服務作為驗證身分的審核資料，比如說銀行開戶，或是電商用於審核賣家是否為台灣居民。&lt;/p&gt;
&lt;p&gt;但由於是雛形的關係，現階段當然不會真的有驗證單位信任這份憑證，未來的正式環境的憑證還是要由比較正式的機關來發行，比如說是內政部或是數位發展部，才可以增進驗證機關對這個憑證的信任程度，甚至會需要有法源依據才可以在更正式的場合使用。&lt;/p&gt;
&lt;p&gt;而在憑證的收藏方面，想像未來會有多張不同的憑證需要收藏時，還會需要有一個 VC Wallet app 來完成這個目的。&lt;/p&gt;
&lt;h3 id=&quot;數位身分的自主性與隱私性&quot;&gt;數位身分的自主性與隱私性&lt;/h3&gt;
&lt;p&gt;前面有提到集中式的數位身分缺乏的是自主性與隱私性，即使在行動自然人憑證服務也有一樣的問題，當一個驗證機關（特別是非公務機關）採用了行動自然人憑證作為驗證手段時，不管是自主性或隱私性都有不同疑慮。比如說之前的例子，開銀行帳戶的時候，政府可以直接屏蔽驗證程序讓特定的使用者無法開戶的自主權疑慮，同時政府也會知道使用者到哪間銀行開戶的隱私疑慮。&lt;/p&gt;
&lt;p&gt;而介接行動自然人憑證並且發出 VC 憑證之後，由於驗證時發行方不用參與的特性，這也讓數位身分的自主性與隱私性得到保障。&lt;/p&gt;
&lt;p&gt;自主性方面，由於發行方沒有參與驗證程序（撤銷清單除外），所以無法透過阻擋驗證程序來抹除一個人的數位身分，即使透過撤銷功能把一個憑證標記為已撤銷，也無法否認發行方曾經簽署、認可過這個數位憑證。&lt;/p&gt;
&lt;p&gt;隱私性方面同樣的因為發行方沒有參與驗證程序，所以發行方是無法得知哪位使用者正在進行驗證程序。&lt;/p&gt;
&lt;p&gt;當然研究與開發時當然都遇到不少問題，下面就來分享一些相關經驗。&lt;/p&gt;
&lt;h3 id=&quot;開發套件&quot;&gt;開發套件&lt;/h3&gt;
&lt;p&gt;首先，在這個專案裡面使用的是 &lt;a href=&quot;https://veramo.io/&quot;&gt;Veramo&lt;/a&gt; 這個 TypeScript 的開發套件，從開發這次專案的經驗來看，Veramo 算是相當有彈性，使用起來也不難。在這次開發裡面我時做了一個新的簽章驗證方式 &lt;code&gt;Semaphore2023&lt;/code&gt;，雖然過程有點糾結，但最終是完成了。&lt;/p&gt;
&lt;p&gt;不過由於這些都還是比較新的開發套件，W3C DIDs/VC 相關的開發者並不像如 React, Vue 之類的熱門開發框架這麼多人關注，所以在文件上並不是那麼齊全，所以要開發非內建的功能時還是要搭配閱讀源碼才有辦法進行。&lt;/p&gt;
&lt;p&gt;由於專案的時間比較趕，所以當初選擇套件的時候並沒有作太深入的研究，後來陸續又看到幾套不同的開發套件如 SpruceID 以 Rust 實作的 &lt;a href=&quot;https://www.sprucekit.dev/&quot;&gt;SpruceKit&lt;/a&gt; 以及 Ceramic 實作的另外一套 TypeScript 函式庫 &lt;a href=&quot;https://github.com/ceramicnetwork/js-did&quot;&gt;ceramicnetwork/js-did&lt;/a&gt;  看起來都是可以嘗試的選擇，特別是 js-did 看起來有&lt;a href=&quot;https://github.com/ceramicnetwork/js-did/tree/main/packages/key-webauthn&quot;&gt;讓 WebAuthn 整合到 did:key 的功能&lt;/a&gt;，值得更深入研究。&lt;/p&gt;
&lt;h3 id=&quot;缺乏-wallet&quot;&gt;缺乏 Wallet&lt;/h3&gt;
&lt;p&gt;在實作的當下並沒有找到適合的 VC Wallet 來收藏發行者所發出的憑證，原本有打算要快速實作一個 Wallet，但是也因為時間的關係沒有做完，可以在 github 裡面找到才剛寫好前端介面，還沒實作邏輯的 &lt;a href=&quot;https://github.com/tw-did/tw-did/tree/main/apps/wallet&quot;&gt;wallet app&lt;/a&gt;。最後我們把驗證程序的選擇憑證功能作在發行方網站裡面，這其實不符合發行方不參與驗證程序的原則，但考慮到實作當時並沒有找到適合的 VC Wallet，這樣算是一個還可以接受的實驗作法。&lt;/p&gt;
&lt;p&gt;雖然當初沒有找到適合的，但在專案結束後才發現微軟平常用來作二階段驗證的 Microsoft Authenticator 有支援收藏 VC 的功能，可以看&lt;a href=&quot;https://learn.microsoft.com/en-us/entra/verified-id/using-authenticator&quot;&gt;這篇文章&lt;/a&gt;瞭解詳情。&lt;/p&gt;
&lt;p&gt;如果更早發現 Microsoft Authenticator 有這個功能時，我們就會參考他們的整合方式，讓憑證可以收藏到 MS Authenticator app 裡面。&lt;/p&gt;
&lt;h3 id=&quot;缺乏交換協議&quot;&gt;缺乏交換協議&lt;/h3&gt;
&lt;p&gt;當初閱讀 W3C 的文件時並沒有規範要如何交換憑證，所以&lt;strong&gt;發行者發行憑證給持有者&lt;/strong&gt;與&lt;strong&gt;持有者出示 VP 給驗證者&lt;/strong&gt;都沒有定義。既然沒有定義，最直觀的方式就是產生一個憑證檔案給使用者下載，而驗證者一樣也是透過網頁原生的檔案選擇功能來提交一個 VC，並且讓使用者進行簽署之後轉換成 VP。&lt;/p&gt;
&lt;p&gt;而在研究 Microsoft Authenticator 的同時，我們也發現了 app 使用的交換協議是另外一個組織 OpenID 訂定的標準 &lt;code&gt;openid-vc&lt;/code&gt; 與 &lt;code&gt;openid-vp&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;當初的實作雖然還是有達到實驗的目的，但如果真的要實作正式產品的時候，可以考慮採用 OpenID 所訂定的標準，就可以使用微軟的 Microsoft Authenticator 作為 VC Wallet 收藏憑證。&lt;/p&gt;
&lt;h3 id=&quot;semaphore-提升了隱私性但是實作細節需要打磨&quot;&gt;Semaphore 提升了隱私性，但是實作細節需要打磨&lt;/h3&gt;
&lt;p&gt;透過 Semaphore 整合確實又大幅度的提升了隱私性，不過由於是實驗的關係，確實有很多實作細節需要打磨。&lt;/p&gt;
&lt;p&gt;舉例來說，我們使用了 &lt;code&gt;did:web&lt;/code&gt; 這個 DID Method，並且在裡面包裝了一個新的驗證型態 &lt;code&gt;Semaphore2023&lt;/code&gt;，這邊實際上是透過 Veramo 擴展了一個新的驗證型態，同時在 Github Pages 上面建立了一個匿名的 DID 身分識別 &lt;a href=&quot;https://tw-did.github.io/hidden/did.json&quot;&gt;did:web:tw-did.github.io:hidden&lt;/a&gt;。這邊更好的方式可能是新增一個新的 DID method &lt;code&gt;did:semaphore&lt;/code&gt;，並且原生支援匿名身分識別。&lt;/p&gt;
&lt;p&gt;同時這邊也會遇到其他套件不會支援 &lt;code&gt;Semaphore2023&lt;/code&gt; 這個特殊的驗證型態，舉例來說前面提到的 MS Authenticator 就無法使用這個我們自行定義的驗證型態。如果可以把整個規格思考清楚，註冊到 &lt;a href=&quot;https://www.w3.org/TR/did-spec-registries/#did-methods&quot;&gt;DID Specification Registries&lt;/a&gt; 並且加以推廣會更適當。&lt;/p&gt;
&lt;p&gt;另外在驗證程序的實作違反了自主權的原則，跟發行方索取了公開資訊 commitments 清單。因為索取的是公開資訊，並不會對隱私性造成問題。但是發行方仍然可以選擇不提供 commitments 來阻擋所有人驗證自己的身分。&lt;/p&gt;
&lt;p&gt;而這個問題可以使用 Semaphore 的鏈上版本解決，因為使用鏈上版本時可以直接跟智能合約互動達成許多事情，也不需要跟發行方索取 commitments 清單，這樣就可已解決自主權的問題。當然如果需要在發行機制裡面使用區塊鏈，也會衍生出其他問題需要解決，比如說交易費的支出問題等等區塊鏈特有的問題。&lt;/p&gt;
&lt;p&gt;總之，整個 Semaphore 整合入 W3C DIDs 的實作細節還有許多需要打磨的地方，可能還需要更多次的迭代與討論才可以找出一個比較成熟的方式。&lt;/p&gt;
&lt;h2 id=&quot;結論&quot;&gt;結論&lt;/h2&gt;
&lt;p&gt;總體來說我覺得這個實驗專案很棒，它透過一個關鍵場景&lt;strong&gt;驗證台灣居民身分&lt;/strong&gt;窺探了未來的模樣，讓關心這個議題的人們（也就是把文章看到這邊的你們）對於更加具有自主性以及隱私性的數位身分有更具體的輪廓。同時在實作的部分也走的夠遠，可以了解到未來正式環境時可能會遇到的阻礙。&lt;/p&gt;
&lt;p&gt;另一方面也更深入的探討如果想要透過零知識證明做到 &lt;strong&gt;匿名但可驗證資格&lt;/strong&gt; 的程度，現在是否具備所需的技術。也可以刺激大家的想像，思考這樣具備高度隱私的技術對我們來說有多重要，以及有沒有其他負面影響。&lt;/p&gt;
&lt;p&gt;當然對我來說也是一個很棒的探索，我自己其實之前沒那麼了解 DID，到這個專案才真正的深入的搞清楚了這些事情。&lt;/p&gt;
&lt;p&gt;自己今年目前的計畫是獨立開發小型的服務或 app，不會跟 DID 或區塊鏈有關，不過還是希望之前的開發經驗能夠發揮一點作用。所以如果你想要針對 DID 這個主題找人討論或交流，還是可以跟我聯絡。我會抽出一點時間在這個主題上，時間上沒辦法做到開發專案但是交流現有經驗跟看法是沒問題的，&lt;a href=&quot;mailto:spry.flag8191@fastmail.com&quot;&gt;歡迎來信&lt;/a&gt;！&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>Semaphore：強化隱私的身分解決方案</title><link>https://yurenju.blog/zh/posts/2024-02-02_semaphore/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2024-02-02_semaphore/</guid><pubDate>Fri, 02 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;當去看演唱會的時候，只要出示演唱會紙本門票就可以進入會場了，偶爾實名制的場合會需要出示身分證，這樣透過紙本門票的方式，工作人員既可以驗證身分而你的足跡又不會被紀錄。&lt;/p&gt;
&lt;p&gt;但是在數位生活的場合就不是這樣。&lt;/p&gt;
&lt;p&gt;在數位生活裡面當你需要證明身分而進行驗證時，幾乎無一倖免的，你的驗證行為會被紀錄。就拿同樣的例子來說，如果進入演唱會會場出示電子門票時，你的入場資訊很大的機率就會被紀錄下來。&lt;/p&gt;
&lt;p&gt;很多時候這些紀錄都是有意義的，而使用者也可以理解與接受，就比如說票券確實要進出紀錄才可以防止一張票券多次使用。但是數位足跡&lt;strong&gt;容易跟蹤&lt;/strong&gt;的特性已經走太遠而有許多不堪其擾的現象，最常見的就是瀏覽網頁之後在 Facebook 與 Google 上面都會充斥著各種相關廣告，就像是走在路上後面有人拿著筆記本貼近著你紀錄一舉一動，你在超商翻過的雜誌，你喜歡喝的飲料。然後反覆在你行徑的路上張貼他覺得你會買單的廣告。&lt;/p&gt;
&lt;p&gt;而以往針對身分的登入與驗證，實作的方式都不會考慮讓這件事情不容易被追蹤，因為有效、容易評估與追蹤就是數位紀錄的長處。但當人們開始重新認知到數位足跡對與隱私的重要性之後，才會回頭發現我們目前常用的數位科技並沒有那種&lt;strong&gt;不容易被跟蹤&lt;/strong&gt;的特性。&lt;/p&gt;
&lt;p&gt;而 &lt;a href=&quot;https://semaphore.pse.dev/&quot;&gt;Semaphore&lt;/a&gt; 就是因此而生的機制。&lt;/p&gt;
&lt;p&gt;Semaphore 是一種透過密碼學當中的零知識證明（Zero Knowledge Proof, ZKP）而實作的身分驗證機制。傳統的數位身分驗證都會需要明確的輸入你的身分識別資訊，比如說登入 Google 帳號時要輸入帳號與密碼，而帳號本身就是你的身分識別資訊了。&lt;/p&gt;
&lt;p&gt;在 Semaphore 的使用情境下，你不會需要輸入&lt;strong&gt;帳號&lt;/strong&gt;這樣的資訊，取而代之的是只驗證&lt;strong&gt;資格&lt;/strong&gt;，而不驗證使用者確切對應的數位身分。讓我們同樣的使用售票平台的場景，但是三種不同的技術實現來解釋這個概念。&lt;/p&gt;
&lt;h2 id=&quot;範例場景&quot;&gt;範例場景&lt;/h2&gt;
&lt;h3 id=&quot;紙本票券&quot;&gt;紙本票券&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;ticket-paper.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2230&quot; height=&quot;1054&quot; src=&quot;/_astro/ticket-paper.CiiKZIXc_Z1eEJx8.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;使用紙本票券的情況下，Alice 在售票網站購票之後取得紙本票券，在活動現場時出示票券給工作人員 Bob，Bob 此時會檢查這張票券是否正確，如果正確的話就讓 Alice 參加活動。&lt;/p&gt;
&lt;p&gt;紙本票券有些固有的缺點，比如說會需要額外的防偽措施來避免其他人造假票券，同時這樣的情境通常也可以用一些電子檢查手段，比如說附上 QR Code 掃描驗證來避免一張票被使用兩次，但採用電子檢查機制時一般來說也會紀錄使用者的足跡。&lt;/p&gt;
&lt;h3 id=&quot;數位票券&quot;&gt;數位票券&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;ticket-digital.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2230&quot; height=&quot;1582&quot; src=&quot;/_astro/ticket-digital.C-aDfr1K_Z2vIW9K.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;使用數位票券的狀況時，Alice 首先要用帳號登入票券平台並且購票。當購票成功後 Alice 會被加入觀眾名單，當到了活動會場的時候，則出示如 QR Code 的手機畫面入場，Bob 用 QR Code 驗證票券正確之後即讓 Alice 入場，並且系統標示 Alice 的這張票券已經入場。&lt;/p&gt;
&lt;p&gt;數位票券的好處就是查核上更為方便，雖然會有技術性的問題要克服，但是入場時可以透過後台的資料庫快速的確認觀眾是否為出席者，即使多人用同一張票券入場也可以偵測到。&lt;/p&gt;
&lt;p&gt;缺點就是數位足跡的問題，合理使用的時候沒問題，但是以現在的情況實在是太濫用了，特別是廣告。&lt;/p&gt;
&lt;h3 id=&quot;semaphore-身分認證&quot;&gt;Semaphore 身分認證&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;cover_ticket-semaphore.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2546&quot; height=&quot;1946&quot; src=&quot;/_astro/cover_ticket-semaphore.CYu0bfVc_WP1h.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;Semaphore 是一個開發套件來協助開發者製作一個&lt;strong&gt;匿名卻可驗證&lt;/strong&gt;的登入機制。首先一樣的會先需要登入票券平台並且購票，但是這邊售票平台則是讓使用者註冊他的 Semaphore Identity，如果有輸入名字時，售票平台依然會知道使用者的名字與對應的 ID 編號，舉例來說 Alice 註冊之後，他的 ID 編號 &lt;code&gt;0x1234&lt;/code&gt; 還是會對應到他的名字 Alice。&lt;/p&gt;
&lt;p&gt;活動當天 Alice 要進去時，同樣的會出示 QR Code 入場，此時工作人員 Bob 一樣也是掃描票券，但是這個 QR Code 並不會包含使用者明確的身分驗證資訊（比如說帳號或是電子郵件），而是會讓驗證方提出一個密碼學挑戰，並且使用者在不揭露身分的前提下回覆一個挑戰解答，這個解答只有是出席者之一才有辦法回答出來，用此來證明他是出席者但卻又不表明自己實際上是哪位。&lt;/p&gt;
&lt;p&gt;這代表驗證票券時，出席名單裡面只會出現類似 &lt;code&gt;user1&lt;/code&gt;, &lt;code&gt;user2&lt;/code&gt;, &lt;code&gt;user3&lt;/code&gt;, &lt;code&gt;user4&lt;/code&gt; 這樣的資訊，而這會分別代表四位出席者，但是 &lt;code&gt;userN&lt;/code&gt; 跟實際出席者的 ID 編號的對應關係是平台無法得知的。&lt;/p&gt;
&lt;p&gt;當 Alice 驗證成功之後，平台只會知道這四個可以入場的出席者裡面，有一個人入場了，還有三個人還沒有入場。至於究竟是哪位入場的，驗證方無從得知，並且這張票券會被標示成已使用，其他人不能拿著同一張票券入場。&lt;/p&gt;
&lt;p&gt;而這些功能的實作，都是基於密碼學裡面零知識證明的技術，用密碼學保證使用者的資格既可以驗證，而又不用揭露確切身分。&lt;/p&gt;
&lt;p&gt;這又比起 W3C DIDs/VC 的機制對於隱私的保護能力又更強了一些，VC 驗證時無可避免的會透漏使用者的公鑰，而 Semaphore 則是不會透漏任何使用者的識別資訊。&lt;/p&gt;
&lt;h1 id=&quot;使用-semaphore-的原因與場景&quot;&gt;使用 Semaphore 的原因與場景&lt;/h1&gt;
&lt;p&gt;如同前面所說，現代常用的數位身分驗證技術都不可避免的會披露使用者的身分，當這些紀錄被串連在一起之後所產生的數位足跡對個人隱私的揭露是一大威脅。如影隨形、不堪其擾的廣告還不夠煩人，就連使用者在兩個完全不同帳號的平台之間切換，比如說社群網站跟部落格網站這樣帳號沒有共通的地方，廣告竟然可以尾隨到各個地方，這些事情惱人到了極致。&lt;/p&gt;
&lt;p&gt;另外一個更令人憂心的是網站入侵事件，當駭客入侵了網站，而使用者在那個網站有許多資料與足跡的時候，這些東西就會被當成詐騙或是下一次入侵的素材散佈在無遠弗屆的網際網路上面。舉例來說，我自己使用網際網路時已經算是比較小心謹慎的人，但是在 Google One 上面的暗網監控功能裡面還是可以看到我的資料大量的被洩漏，而這些大多都是網站被入侵後放到暗網上面販賣。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;google-one-results.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2258&quot; height=&quot;1894&quot; src=&quot;/_astro/google-one-results.q-X4nnR0_Z1g1cj.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;而當使用 Semaphore 這樣技術的網站變多了之後，由於網站上面儲存的資料最小化，這樣一來連網站本身也無法識別使用者的數位足跡的狀況下，即使被盜取資料也沒有用處了。&lt;/p&gt;
&lt;p&gt;當然這樣匿名又可以驗證的技術並沒辦法套用在所有場景，除了購票以外，這邊舉幾個例子是可以使用 Semaphore 作為認證的，不過 Semaphore 只能解決隱私問題，通常例子裡面都會有很多複雜的情況需要處理，但我們這邊就只聚焦在隱私問題。&lt;/p&gt;
&lt;h3 id=&quot;網路買酒&quot;&gt;網路買酒&lt;/h3&gt;
&lt;p&gt;一般在網路買酒的時候不會希望被知道你的個人隱私身分資訊，但是又需要驗證你的年紀資訊，如果可以在特定服務驗證身分之後，具備成年資格的人都可以獲得一個 Semaphore 規範的資格證明，未來網路買酒的時候只要出示這個證明就可以了，並且不會透過確切身分識別被追蹤到你的數位足跡。&lt;/p&gt;
&lt;p&gt;這邊驗證年紀時，由於只能知道是否符合資格，但卻不知道確切身分，所以通路商那邊也不會儲存任何隱私資料。當然提供身分驗證的服務還是有隱私資料，但是攻擊面就會小得多。&lt;/p&gt;
&lt;h3 id=&quot;吹哨者平台&quot;&gt;吹哨者平台&lt;/h3&gt;
&lt;p&gt;不管是性騷擾或是各種吹哨者平台，通常都會需要驗證該使用者有資格可以回報特定事件，但是又需要保持匿名。想像如果公司登記平台時，會需要把員工透過 semaphore 身分登錄系統內，當需要舉報時，Semaphore 身分驗證就可以作為一個既可以驗證，但又保持匿名的身分驗證方式。&lt;/p&gt;
&lt;p&gt;舉例來說性騷擾防治平台在舉報時，可以確認舉報的人真的有在那間公司工作過，但是又可以保持匿名性。&lt;/p&gt;
&lt;h3 id=&quot;藥物或是清寒補助&quot;&gt;藥物或是清寒補助&lt;/h3&gt;
&lt;p&gt;有些藥物補助跟清寒補助由於社會上長期有污名化問題時，也可以用類似的方法解決。比如說可以透過 Semaphore 建立補助資格清單，但是領取補助的時候不需要實際出示身分，但是卻可以確認他具備領取補助的資格。&lt;/p&gt;
&lt;h3 id=&quot;意見調查&quot;&gt;意見調查&lt;/h3&gt;
&lt;p&gt;意見調查（或是說電子投票）也是一個經常會需要驗證身分，但是在發表意見時又需要保持匿名的場景。不過意見調查要解決的問題還很多，隱私只是其中一個大問題，還有更多問題需要解決。關於這個議題我推薦看 Tom Scott 的 Youtube 影片《&lt;a href=&quot;https://www.youtube.com/watch?v=LkH2r-sNjQs&quot;&gt;Why Electronic Voting Is Still A Bad Idea&lt;/a&gt;》。&lt;/p&gt;
&lt;p&gt;上面是一些我想到的例子，但是普遍來說只要場景是&lt;strong&gt;匿名但又需要資格驗證&lt;/strong&gt;都可以採用 Semaphore 作為開發套件。&lt;/p&gt;
&lt;h2 id=&quot;結論&quot;&gt;結論&lt;/h2&gt;
&lt;p&gt;Semaphore 作為一個採用零知識證明建構的開發套件，其實可以在隱私上面有很多不同的應用，當然在隱私上面提升了，這也就代表縮限了平台方的權力。&lt;/p&gt;
&lt;p&gt;在上篇文章《&lt;a href=&quot;https://yurenju.blog/posts/2024-01-01_w3c-dids-redefining-identity-authority/&quot;&gt;W3C DIDs：拆解權力結構的數位身分標準&lt;/a&gt;》發表的迴響裡面有一則讓我印象深刻，他提到了這樣的烏托邦式的機制很美好，但不會有人用。我其實部分同意他的說法，因為這樣的機制確實巨型企業的參與者不投入的話，其實不容易取得成功，而巨型企業為什麼要縮限自己已經大權在握的權力呢？&lt;/p&gt;
&lt;p&gt;但由於數位足跡與個人隱私的問題深深地影響到每個人，逐漸的也引起了各方面的關注。比如說歐盟的 GDPR 開始更嚴格的限制隱私資料的使用，甚至到最近的 &lt;a href=&quot;https://github.com/eu-digital-identity-wallet/eudi-doc-architecture-and-reference-framework?tab=readme-ov-file&quot;&gt;歐盟數位身分識別皮夾（The European Digital Identity Wallet, EUDIW）&lt;/a&gt; 也將會採用 W3C Verifiable Credentials 1.1 標準，進一步的從跨國家的法規與監管角度加強隱私保護。&lt;/p&gt;
&lt;p&gt;而相對不靠隱私營利的蘋果也在近幾年導入 App 追蹤許可，讓使用者可以自行決定要不要揭露自己的數位足跡給 app，這些都是讓數位足跡問題慢慢修復的跡象。&lt;/p&gt;
&lt;p&gt;我是相對悲觀的人，我也同意這些隱私相關的進展並不會很快的就改變世界，但看到這些進展的時候還是會保有一點希望，總是不能因為胖虎一直都欺負大雄，就悲觀的跟老師說他不懂胖虎吧。改變眾人看法或是習慣是很困難的事情，我也希望可以提供一點能力所及的協助。&lt;/p&gt;
&lt;p&gt;回過頭來，Semaphore 作為一個開發套件，他的功能是讓開發者可以開發出更符合隱私原則的身分驗證機制，但他並不像 W3C DIDs 是一個標準，而是更底層的開發套件，所以還需要開發者搭建出更完整的隱私優先的身分驗證機制。&lt;/p&gt;
&lt;p&gt;現在有一些專案慢慢的引進了 Semaphore 的身分驗證機制。&lt;/p&gt;
&lt;p&gt;例如 &lt;a href=&quot;https://github.com/proofcarryingdata/zupass&quot;&gt;zuzalu passport (zupass)&lt;/a&gt; 作為一個研討會/活動的身分驗證平台，採用了 Semaphore 作為 zupass 的身分驗證基礎建設，並且發布了 Proof-Carrying Data (PCD) SDK 作為憑證的解決方案，zupass 像是一個輕量型的 DID 方案，但並沒有特意去符合 W3C DIDs/VC 標準。&lt;/p&gt;
&lt;p&gt;另外 &lt;a href=&quot;https://developer.unirep.io/&quot;&gt;UniRep&lt;/a&gt; 作為一個匿名的聲譽系統可以接收與授予聲譽，同樣也是採用了 Semaphore 作為底層的身分識別方案。&lt;/p&gt;
&lt;p&gt;總體來說 Semaphore 還是一個非常新穎的解決方案，而雖然從開發者的角度來說 Semaphore 已經將用於身分驗證的零知識證明框架包裝得非常容易使用，但是這些密碼學技術還是個前沿領域，開發者還是需要理解整個技術的概念，同時也需要謹慎的透過實驗性嘗試來評估開發套件的各種面向。&lt;/p&gt;
&lt;p&gt;所以 Semaphore 距離大規模應用還會有一段路需要走，但是隨著更多人關注這樣關注隱私的技術，我相信網際網路上面的隱私問題會有改善的機會。&lt;/p&gt;
&lt;h3 id=&quot;to-be-continued&quot;&gt;To Be Continued…&lt;/h3&gt;
&lt;p&gt;而至於我會什麼要在 W3C DIDs 系列文章介紹 Semaphore 呢？主要是因為我去年下半年作的一個專案融合了內政部的行動自然人憑證、W3C DIDs 以及 Semaphore 零知識證明框架，實驗性的驗證這幾個不同技術整合的可能性，同時評估各種優缺點。&lt;/p&gt;
&lt;p&gt;這其實將 W3C DIDs 與 VC 與 Semaphore 隱私保護疊加了起來，將隱私保護更往前拓展到更前沿與未知的邊緣，而在這樣的狀態，我們才更容易觀察到這些技術的各種面向。&lt;/p&gt;
&lt;p&gt;但是不論是 W3C DIDs/VC、Semaphore 甚至連&lt;strong&gt;去中心化身分識別&lt;/strong&gt;都非常難以一次講清楚，所以才有了這個系列文章鋪陳所有的知識，而下篇文章會是系列的最後一篇，希望這個月有機會可以寫完並且發布，敬請期待！&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>W3C DIDs：拆解權力結構的數位身分標準</title><link>https://yurenju.blog/zh/posts/2024-01-01_w3c-dids-redefining-identity-authority/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2024-01-01_w3c-dids-redefining-identity-authority/</guid><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;封面照片&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1200&quot; height=&quot;960&quot; src=&quot;/_astro/cover_w3c-dids.D2T3-6Yb_Z28QGsx.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;上一篇相關文章《&lt;a href=&quot;https://yurenju.blog/posts/2023-08-21_fb-ban-and-did-solution/&quot;&gt;從 Facebook 無端封鎖帳號來看數位身分的問題與 DID 解決方案&lt;/a&gt;》從我被 Facebook 無端停權的慘劇中重新審視了數位身分的現狀，並且簡短的提到 &lt;a href=&quot;https://w3c.github.io/did-core/&quot;&gt;W3C DIDs&lt;/a&gt; 這個企圖打破這個現狀的標準，接下來想要更深入的來探討 W3C DIDs 的機制如何改變現狀。&lt;/p&gt;
&lt;p&gt;還沒閱讀&lt;a href=&quot;https://yurenju.blog/posts/2023-08-21_fb-ban-and-did-solution/&quot;&gt;上篇文章&lt;/a&gt;的讀者可以先回頭重溫一下 😎&lt;/p&gt;
&lt;h2 id=&quot;現況&quot;&gt;現況&lt;/h2&gt;
&lt;p&gt;從我的角度來看現在的數位身分服務有幾個問題：&lt;/p&gt;
&lt;h3 id=&quot;自主性&quot;&gt;自主性&lt;/h3&gt;
&lt;p&gt;目前的數位身分不是由使用者擁有，而是由大企業擁有，而他們&lt;strong&gt;允許&lt;/strong&gt;你使用那個數位身分，就像我 Facebook 帳號被永久刪除卻不需要取得我同意的情況。&lt;/p&gt;
&lt;h3 id=&quot;隱私性&quot;&gt;隱私性&lt;/h3&gt;
&lt;p&gt;身分識別提供商（也就是 Google 與 Facebook 等企業）可以追蹤使用者的登入行為，而使用者的數位足跡就是他們的營利工具，透過使用者的數位足跡可以成為他們廣告的養分獲得豐厚的利潤。配合上企業擁有的其他服務如 Google 搜尋引擎、Facebook 社群網路搭配上嵌入在網站的 Facebook 留言功能就足夠拼湊起使用者的模樣，然後提供你喜歡的廣告，甚至餵給你&lt;a href=&quot;https://www.youtube.com/watch?v=PAr1F5keUGw&quot;&gt;更容易上鉤的詐騙廣告&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;但 DIDs 要怎麼解決這些問題呢？首先讓我們更進一步的了解 DIDs。&lt;/p&gt;
&lt;h2 id=&quot;w3c-dids&quot;&gt;W3C DIDs&lt;/h2&gt;
&lt;p&gt;W3C 去中心化身分識別（Decentralized Identifiers, DIDs）是一個&lt;strong&gt;去中心化身分識別&lt;/strong&gt;的標準，使用者可以使用一個基於密碼學的數位身分來進行身分驗證。實際上使用 W3C DIDs 的時候，經常需要配合另外一個標準 &lt;strong&gt;W3C 可驗證憑證&lt;/strong&gt;（Verifiable Credentials, VCs） 。兩個的標準分別負責不同功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DIDs 標準：如何驗證一個 DID 身分識別&lt;/li&gt;
&lt;li&gt;VCs 標準：一個 DID 要替另外一個 DID 的聲明背書時所發出的憑證&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;舉例來說，Alice 在一個遊戲購買平台（就叫這個平台 Stream 吧）購買一個由遊戲開發商 “SE” 開發的遊戲《最先幻想》時，Stream 會發給 Alice 一個遊戲購買證明。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;VC 與 DID 使用遊戲購買的範例&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1464&quot; height=&quot;806&quot; src=&quot;/_astro/did-game-example.D-OsAG46_ZgU9vi.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;這個情境底下 Stream 作為平台方會擁有一個 DID 身分識別，而 Alice 也會有一個作為個人的 DID 身分識別。Stream 在 Alice 購買遊戲之前，會先從 Alice 的 DID 識別裡面找出如何驗證的資訊，並且驗證這個 Alice DID 識別，DID 標準就是制定這些要如何驗證的資訊。&lt;/p&gt;
&lt;p&gt;當 Alice 通過 DID 驗證並且購買遊戲後，Stream 會發給 Alice 一個採用 VC 標準格式的遊戲購買證明，裡面由 Stream 的 DID 作為發行者（Issuer）證明 Alice 在 Stream 上面購買了遊戲《最先幻想》，這樣針對一個聲明背書所產生的憑證。往後收到這個憑證的系統可以根據裡面的資訊驗證 VC 裡面記載的資訊是否正確無誤，這就是 VC 標準所提供的功能。&lt;/p&gt;
&lt;p&gt;一個 DID 識別長得像這樣：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;did-identifier&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1442&quot; height=&quot;450&quot; src=&quot;/_astro/did-identifier.iX90NUK5_1m4Vg2.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;DID 識別由三個欄位組成並且以冒號分隔欄位。第一個欄位固定是字串 &lt;code&gt;did&lt;/code&gt;，第二個欄位則是 DID Method，由於基於密碼學的數位身分有非常多不同的種類與平台，有區塊鏈平台、非區塊鏈平台、使用者放在自己網站上的公鑰資訊、甚至本地產生的公鑰也支援，這些不同的 DID 種類的存取方式稱為 DID Method，而第二個欄位就是標記這個 DID 識別使用了哪種 DID Method。第三個欄位則是針對特定 DID Method 的識別字串。&lt;/p&gt;
&lt;p&gt;舉例來說以下的 DID 識別：&lt;code&gt;did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a&lt;/code&gt; 代表的意思是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;did&lt;/code&gt;: 這是一個 DID 識別&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ethr&lt;/code&gt;: 這個 DID 識別採用的識別方式是 &lt;code&gt;ethr&lt;/code&gt; 這個 DID Method&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0xb9c5...6e8a&lt;/code&gt;: 使用者在這個 DID Method 用這個字串代表特定身分識別（可以想像是身分證字號）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;而這個 DID 識別字串經過合適的函式庫解析之後就可以得知要如何跟這個 DID 互動，比如說如何驗證使身分或是驗證憑證，這些互動資訊會是一個 JSON 檔案，稱為 DID Document。&lt;/p&gt;
&lt;p&gt;這邊介紹幾個不同的 DID Method：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;did:key&lt;/code&gt;：使用者直接在 DID 識別字串裡提供的公鑰，沒有把公鑰資訊放在其他地方&lt;/li&gt;
&lt;li&gt;&lt;code&gt;did:web&lt;/code&gt;：使用者把公鑰資訊放在自己的網站上面&lt;/li&gt;
&lt;li&gt;&lt;code&gt;did:ethr&lt;/code&gt;：使用者指定自己在 Ethereum 區塊鏈上面的帳號地址作為 DID 識別&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了以上三種以外 DID Method 有非常多種，更多種類請參考 &lt;a href=&quot;https://www.w3.org/TR/did-spec-registries/&quot;&gt;DID Specification Registries&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&quot;didkey&quot;&gt;did:key&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;did:key&lt;/code&gt; 是指 DID 識別的字串裡面就自帶了公鑰資訊，所以不需要區塊鏈或是其他存放公鑰的地方。舉例來說一個 DID 識別 &lt;code&gt;did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK&lt;/code&gt; 的第三個部分 &lt;code&gt;z6Mk...2doK&lt;/code&gt; 就是一個演算法的公鑰，這樣的資訊就足夠來驗證這個 DID 識別了。要判斷是哪一種加密演算法，可以從第三個欄位開頭判斷，舉例來說：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;z6Mk&lt;/code&gt;: Ed25519 演算法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;zQ3s&lt;/code&gt;: Secp256k1 演算法&lt;/li&gt;
&lt;li&gt;&lt;code&gt;zDn&lt;/code&gt;: P-256 演算法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以上面的例子 &lt;code&gt;z6Mk...2doK&lt;/code&gt; 就可以辨識需要採用 Ed25519 演算法來驗證訊息。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;did:key&lt;/code&gt; 的好處是非常簡易，並且由使用者自行保管，如果包裝成 app 甚至可以使用生物識別裝置如 TouchID 來安全的存放私鑰，算是非常方便的選擇。缺點是比較沒有彈性，比如說你想要幾個不同裝置如手機、電腦來管理一個 DID 身分並沒有一個安全的管理方式。&lt;/p&gt;
&lt;p&gt;另外一個缺點是 &lt;code&gt;did:key&lt;/code&gt; 目前的格式並不支援 &lt;a href=&quot;https://webauthn.io/&quot;&gt;WebAuthn&lt;/a&gt; 的格式，所以沒辦法在網頁上直接用生物識別裝置來管理 &lt;code&gt;did:key&lt;/code&gt;，必須要包裝成 mobile app。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2024-02-05 更新&lt;/strong&gt;：最近看到 &lt;a href=&quot;https://github.com/ceramicnetwork/js-did/tree/main/packages/key-webauthn&quot;&gt;ceramicnetwork/js-did/key-webauthn&lt;/a&gt; 看起來有可能用 WebAuthn 來產生與驗證 &lt;code&gt;did:key&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&quot;didweb&quot;&gt;did:web&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;did:web&lt;/code&gt; 是使用者透過上傳一個 JSON 到特定網址來提供 DID 互動資訊的方式，舉例來說 DID 識別 &lt;code&gt;did:web:mattr.global&lt;/code&gt; 實際上會從網址 &lt;a href=&quot;https://mattr.global/.well-known/did.json&quot;&gt;https://mattr.global/.well-known/did.json&lt;/a&gt; 獲得 DID 識別資訊。這個 DID 的擁有者可以藉由改變這個放在網站上面的 JSON 檔案來調整他對身分驗證的需求，比如說他可以在多個裝置都擁有不同的密碼學鑰匙，讓他可以在手機、電腦、平板電腦上面都可以登入，或是他想要定期的汰換更新鑰匙來保持安全性也都可以做到。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;did:web&lt;/code&gt; 的好處也是很簡單，並且要更新也非常方便，我想因為大家覺得自己&lt;strong&gt;擁有&lt;/strong&gt;那個網址，所以會覺得滿安全的。缺點是針對一般人不算是特別方便，簡易的方法可能是在 Github pages 裡面上傳一個檔案，對於工程師來說很簡單，對於一般使用者會稍有難度。&lt;/p&gt;
&lt;p&gt;但從&lt;strong&gt;去中心化&lt;/strong&gt;的角度來說 &lt;code&gt;did:web&lt;/code&gt; 有一些缺點。比如說如果是放在 Github Pages 時，當你跟 Github 產生利益衝突，官方決定移除掉你的網站後，你的數位身分也被奪走了。更別說網域名稱即使註冊了也是有期間限制，同時你也有可能跟網域商有利益衝突而導致網域失效。&lt;/p&gt;
&lt;p&gt;當然從大多數人的角度來看，這個方法已經夠有自主權了，但如果你擔心的話，可以使用稍微沒有彈性的 &lt;code&gt;did:key&lt;/code&gt;，或是看看下一個解決方案 &lt;code&gt;did:ethr&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&quot;didethr&quot;&gt;did:ethr&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;did:ethr&lt;/code&gt; 是一個把 DID 識別的互動資訊放在 Ethereum 或相容區塊鏈上面的一種 DID Method，舉例來說 DID 識別 &lt;code&gt;did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74&lt;/code&gt;，第三個部分就是帳號地址，使用者就可以用控制這個帳號的私鑰來驗證這個 DID 識別，驗證的方法預設就是針對一個訊息簽名，這是一個 Ethereum 區塊鏈使用者經常會作的事情，也很直覺。&lt;/p&gt;
&lt;p&gt;除了開箱即用以外，他還可以透過一個&lt;a href=&quot;https://etherscan.io/address/0xdca7ef03e98e0dc2b855be647c39abe984fcf21b&quot;&gt;智能合約&lt;/a&gt;來進階設定這個 DID，也可以做到多裝置/私鑰管理同一個 DID 識別以及進行鑰匙的更新汰換等進階功能。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;did:ethr&lt;/code&gt; 的優點在於跟 &lt;code&gt;did:web&lt;/code&gt; 一樣有更多選項可以調整 DID 識別，同時足夠&lt;strong&gt;去中心化&lt;/strong&gt;，保證了只要擁有相對應的私鑰就可以完整的控制 DID 識別，中間沒有其他中間人如雲端伺服器廠商或是網域商可能跟使用者有利益衝突而奪走你的數位身分。&lt;/p&gt;
&lt;p&gt;但缺點也很顯而易見，由於 &lt;code&gt;did:ethr&lt;/code&gt; 是採用區塊鏈技術，沒有接觸過區塊鏈的使用者會覺得門檻太高不容易使用。&lt;/p&gt;
&lt;h2 id=&quot;verifiable-data-vc&quot;&gt;Verifiable Data (VC)&lt;/h2&gt;
&lt;p&gt;VC 跟 DID Document 一樣都是一個 JSON 檔案，如同前面的敘述所說 VC 是一個 DID 發給另外一個 DID 的憑證來為一個特定聲明背書。如果一樣以遊戲購買平台 Stream 的例子來看，Stream 的 DID 可能是 &lt;code&gt;did:web:streamgame.com&lt;/code&gt;，而 Alice 如果選擇用他自己的 Ethereum 區塊鏈帳號來代表他的身分，那 DID 就會類似 &lt;code&gt;did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;這邊也可以觀察到 VC 裡面的&lt;strong&gt;發行者&lt;/strong&gt;跟&lt;strong&gt;授予者&lt;/strong&gt;可以使用不同 DID Method，所以一個把公鑰儲存在網站的發行者，也可以把 VC 憑證發行給一個使用 Ethereum 帳號地址作為 DID 識別代表的使用者。&lt;/p&gt;
&lt;p&gt;當 Alice 購買遊戲後，Stream 的系統就會發行一個 VC 遊戲購買憑證給 Alice，範例如下：&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;  &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;@context&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;https://www.w3.org/2018/credentials/v1&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;  &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;issuer&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;    &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;did:web:streamgame.com&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;  &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;credentialSubject&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;    &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;    &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;purchaseId&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;123456789&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;    &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;gameTitle&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;First Fantasy&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;    &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;purchaseDate&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;2023-12-31T15:00:00Z&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;    &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;paymentAmount&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#4C9A91&quot;&gt; 59.99&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;    &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;currency&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;USD&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;    &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;platform&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;PC&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;  &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;VerifiableCredential&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;  &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;issuanceDate&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;2023-10-30T07:57:06.000Z&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;  &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;proof&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;    &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;JwtProof2020&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;    &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;jwt&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;eyJhbGci...2rzP0K5wow&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;這個範例裡面描述了發行方（Issuer）是 &lt;code&gt;did:web:streamgame.com&lt;/code&gt;，購買遊戲的人是 Alice 的 DID &lt;code&gt;did:ethr:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74&lt;/code&gt;，並且在 &lt;code&gt;credentialSubject&lt;/code&gt; 欄位有遊戲購買資訊。&lt;/p&gt;
&lt;p&gt;這邊重要的是 &lt;code&gt;proof&lt;/code&gt; 欄位裡面是發行方針對這個 VC 的簽署資訊，任何人拿到這個檔案之後都可以驗證這個檔案是否確實是由 Steam 所簽署與發布。Alice 取得這個憑證之後，就可以跟其他人或是廠商出示來證明他擁有這個遊戲。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;附上簽名的 VC 遊戲購買憑證&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1670&quot; height=&quot;704&quot; src=&quot;/_astro/did-game-example-with-signature.CetsH4-X_JBsTA.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;舉例來說他購買的遊戲《最先幻想》的遊戲廠商 “SE” 推出了下個世代的遊戲《中間幻想》，而新版本並不在 Stream 上面販售，改在 “SE” 的官方網站可以購買數位版本，但是買家可以透過《最先幻想》的購買證明來享有《中間幻想》的六折優惠。&lt;/p&gt;
&lt;p&gt;另外由於在 &lt;code&gt;credentialSubject&lt;/code&gt; 的 &lt;code&gt;id&lt;/code&gt; 屬性有紀錄了 Alice 的 DID 識別，“SE” 官方網站在確認使用者是 VC 當中所紀錄的購買人時，也可以請求使用者 DID 的簽名，確保他就是這筆購買證明的購買者，而不是竊取的購買證明。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;透過折扣購買遊戲前請求購買人簽名&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1534&quot; height=&quot;640&quot; src=&quot;/_astro/did-game-example-for-discount.CtD_3_58_ZbkDd7.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;接下來我們用同一個範例來檢視現行身分驗證機制與採用 DID 身分驗證機制的不同之處。&lt;/p&gt;
&lt;h2 id=&quot;範例現行身分驗證與-did-的差異&quot;&gt;範例：現行身分驗證與 DID 的差異&lt;/h2&gt;
&lt;p&gt;在上面這個例子裡 Alice 從遊戲購買平台 Stream 購買了《最先幻想》，新版本的遊戲《中間幻想》則在 “SE” 官網上面販售數位版本，而且只要以前在 Stream 購買過上個版本《最先幻想》就可以用六折優惠購買《中間幻想》。&lt;/p&gt;
&lt;h3 id=&quot;現行身分驗證機制&quot;&gt;現行身分驗證機制&lt;/h3&gt;
&lt;p&gt;如果是現行的身分驗證機制，Stream 遊戲購買平台會提供一個登入 Stream 平台的功能，通常會採用類似 OpenID Connect (OIDC) 的標準提供這個功能。當在遊戲官方網站透過 OIDC 連結 Stream 之後，再透過 Stream 的 APIs 來取得使用者的購買資訊。&lt;/p&gt;
&lt;p&gt;接下來我們來探討一下這個方法在自主性與隱私性上面有什麼問題：&lt;/p&gt;
&lt;h4 id=&quot;自主性-1&quot;&gt;自主性&lt;/h4&gt;
&lt;p&gt;如果 Stream 因為年代久遠下架了《最先幻想》，或是 Alice 跟 Stream 之間有什麼利益衝突（比如說 Alice 幫敵對平台 Epico 打廣告）進而宣稱 Alice 「違反平台規範」而停權了他的帳號，“SE” 官方網站是無法透過其他方式來取得 Alice 在 Stream 上面的資料。&lt;/p&gt;
&lt;p&gt;這個狀況就跟我遇到 Facebook 完全無理由的將我的帳號永久停權一樣，我並沒有真的 &lt;strong&gt;擁有&lt;/strong&gt; Facebook 上面的數位身分。&lt;/p&gt;
&lt;h4 id=&quot;隱私性-1&quot;&gt;隱私性&lt;/h4&gt;
&lt;p&gt;當遊戲官方網站因為《中間幻想》的折扣販售需要透過 Stream APIs 進行登入、查詢購買紀錄，Stream 將會得知 Alice 在 “SE” 官方網站商店的數位足跡。如果 Stream 也是一間利用使用者數位足跡營利的遊戲平台（比如說他有提供遊戲廣告），那麼他就有強烈的動機來紀錄使用者的數位足跡。&lt;/p&gt;
&lt;p&gt;接下來我們來看看如果採用 W3C DIDs 會是什麼情況。&lt;/p&gt;
&lt;h3 id=&quot;w3c-dids-身分驗證機制&quot;&gt;W3C DIDs 身分驗證機制&lt;/h3&gt;
&lt;p&gt;如果採用 W3C DIDs 實作購買證明機制時，當 Alice 購買遊戲《最先幻想》時，就會根據 Alice 的 DID 識別發布一個採用 Verifiable Credential 格式的遊戲購買證明給 Alice，這個購買證明裡面會包含 Stream 發行方的簽章、購買人資訊以及遊戲資訊。&lt;/p&gt;
&lt;p&gt;當 Alice 想要從 “SE” 官方遊戲平台購買新版本的《中間幻想》需要檢查在 Stream 平台的購買資格時，僅需要把該遊戲購買憑證 VC 提供給 “SE” 官方網站，此時 “SE” 官方網站會驗證該憑證是否為 Stream 發行，並且是正確的遊戲購買憑證以及藉由購買人簽名驗證 Alice 是否擁有購買時使用的特定 DID 識別。&lt;/p&gt;
&lt;p&gt;驗證完 VC 格式的購買證明之後，“SE” 遊戲官方網站就可以提供優惠給 Alice 了。接下來我們一樣來審視一下自主性以及隱私性。&lt;/p&gt;
&lt;h4 id=&quot;自主性-2&quot;&gt;自主性&lt;/h4&gt;
&lt;p&gt;當 Stream 提供 VC 購買憑證給 Alice 之後，上面會附有 Stream 發行方的簽名，這個簽名是無法否認的，證明 Stream 發行方肯定有簽名過。&lt;/p&gt;
&lt;p&gt;W3C DIDs 標準其實有提供撤銷機制，但這個撤銷機制僅是透過&lt;strong&gt;撤銷清單&lt;/strong&gt;讓發行方將此憑證標記為&lt;strong&gt;撤銷狀態&lt;/strong&gt;，之前附在 VC 上面的簽名依然是無法否認。&lt;/p&gt;
&lt;p&gt;即使 Stream 標注了 Alice 的購買證明已經被撤銷，但是以前 Alice 所保存的購買資訊憑證仍然可以證明他曾經在 Stream 上面購買過這個遊戲，因為所有需要的資訊都在憑證裡面了。雖然撤銷機制會把這個憑證標示成 &lt;strong&gt;已撤銷&lt;/strong&gt;，但並沒有辦法否認 Stream 發行方以前曾經對這個憑證簽名過，所以憑證的發行方簽名依然有效，只是多了一個 &lt;strong&gt;已撤銷&lt;/strong&gt; 的標記。&lt;/p&gt;
&lt;p&gt;這樣販售《中間幻想》的 “SE” 官方網站可以自行決定要不要接受這樣的憑證，而不是把權力都集中在發行方上面。&lt;/p&gt;
&lt;h4 id=&quot;隱私性-2&quot;&gt;隱私性&lt;/h4&gt;
&lt;p&gt;當 Alice 提供憑證給遊戲官方平台時，Stream 如果選擇把發行方公鑰資訊放在網站上，頂多只能知道有人下載了公鑰資訊，並不知道是哪個使用者要拿來驗證哪個遊戲，如果放在 Ethereum 區塊鏈上面的時候，Stream 甚至不知道有人下載過公鑰資訊。在隱私權的保障上就比起現行的實作方式要更好。&lt;/p&gt;
&lt;p&gt;這兩個特性在傳統驗證機制與 DID 驗證機制的差異如下：&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;特性&lt;/th&gt;&lt;th&gt;傳統身分驗證機制&lt;/th&gt;&lt;th&gt;W3C DIDs 身分驗證機制&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;自主性&lt;/td&gt;&lt;td&gt;平台有權決定撤銷使用者的憑證或身分，並且拒絕任何資料存取&lt;/td&gt;&lt;td&gt;平台只能標記憑證為已撤銷，但是無法否定他曾經簽名過的憑證&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;隱私性&lt;/td&gt;&lt;td&gt;透過 APIs 登入或查詢資料時，平台可以獲得使用者的數為足跡&lt;/td&gt;&lt;td&gt;平台無法透過登入行為追蹤使用者的數位足跡&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;結論隱私與自主得到了保障還缺什麼&quot;&gt;結論：隱私與自主得到了保障，還缺什麼？&lt;/h2&gt;
&lt;p&gt;承上所述，W3C DIDs 以及 Verifiable Credentials 標準確實提升了使用者對於身分驗證的自主性與隱私性，這個機制既然這麼好，應該現在就要立即導入 DID，我們還在等什麼呢？&lt;/p&gt;
&lt;p&gt;DIDs 是一個剛成形不久的標準協議，其實還是有很多不足的地方。首先如果需要更好的支援 DID 已及 VC，我們會需要一個可以收納各種 VC 憑證的 app，並且在需要使用的時機可以透過這個 app 來選取自己這次要使用的憑證，而這個 app 還會需要可以管理使用者的私鑰，這個 app 本質上會很像 Apple Wallet 或是 Google Wallet 這樣用來收藏票券以及會員卡的錢包，但是還會需要額外有管理內部或是外部私鑰的功能實作。&lt;/p&gt;
&lt;p&gt;現實中並沒有一個成熟好用的 app 來作這件事情，更何況 DID 還在起步階段，到底是要先有 VC Wallet 還是先支援 DID 就變成先有雞還是先有蛋的問題。&lt;/p&gt;
&lt;p&gt;而下一個問題就是&lt;strong&gt;動機&lt;/strong&gt;。就如同上面的敘述，採用 DID 之後，身分識別提供商或是憑證發行方的權力都會大幅縮減，並且更難追蹤使用者的數位足跡。這邊可以換位思考一下如果 Google 或是 Facebook 實作一個新的身分驗證協議會大幅度的降低廣告的精準度，進而導致主要營收來源的獲利減低，同時又失去了更多對於使用者的控制權，那他們會有什麼動機來換成 DIDs 呢？&lt;/p&gt;
&lt;p&gt;比較好的切入點可能會是從監管的角度來看，由主管機關比如說歐盟規範巨型企業必須縮減對數位世界的掌控權，甚至由政府針先行針對官方憑證如身分證或是駕照採用 DID 標準，我認為是比較好的切入角度。&lt;/p&gt;
&lt;p&gt;回過頭來說 DIDs 還非常的早期，要面對的缺點實在很多。但反過來看他也是充滿希望的地方，這個標準針對目前巨型企業對於數位世界的巨大掌控權進行了反思，並且描繪了一個更加自主與具備隱私的藍圖，讓我們可以藉此思考是否有機會可以透過標準來將一些原本就該屬於使用者的權力收回。&lt;/p&gt;
&lt;p&gt;當然我們對於未來的想像也可不止於此，我們可以再推進一步來看：如果還想要更好的隱私，現在有什麼解決方案可以達成？舉例來說，如果我們驗證使用者是否曾經在 Stream 購買過《最先幻想》時，是不是連自己的 DID 識別都不用揭露，也可以驗證使用者確實購買過該遊戲呢？&lt;/p&gt;
&lt;p&gt;下一篇文章將會講解 &lt;a href=&quot;https://semaphore.appliedzkp.org/&quot;&gt;Semaphore&lt;/a&gt; 這個零知識證明的框架如何達成更進一步的隱私應用，我們下回分曉！&lt;/p&gt;
&lt;h2 id=&quot;補充資訊&quot;&gt;補充資訊&lt;/h2&gt;
&lt;h3 id=&quot;didweb-無痕更換鑰匙的問題&quot;&gt;did:web 無痕更換鑰匙的問題&lt;/h3&gt;
&lt;p&gt;Luoh Ren-Shan 在 &lt;a href=&quot;https://www.facebook.com/yurenju/posts/pfbid0916c4YdTULXymUW68pE32nuNTYVHAXcpkV4NZEuM7ZodGBHz1NEKpaBgrmLLG6VEl?comment_id=881600443684722&quot;&gt;Facebook 的評論&lt;/a&gt; 提到如果發行方 Stream 採用 &lt;code&gt;did:web&lt;/code&gt; 作為 DID 時，發行方是可以無痕的更改自己的鑰匙資訊，來讓 Alice 手上的憑證失效。&lt;/p&gt;
&lt;p&gt;這確實是一個問題，但是這樣更新鑰匙資訊時，Stream 其他用原本鑰匙簽發的憑證也會一併失效，並不能只限制在 Alice 的憑證，這就會產生對於 Stream 發行方的信任危機，身為使用 &lt;code&gt;did:web&lt;/code&gt; 的發行方就會需要在好壞權衡裡面考量是否要這麼做。當然如果出大包的時候發行方可能還是會出這招來激進的處理棘手問題，但相較於傳統的方法可以輕易的操縱結果，使用 &lt;code&gt;did:web&lt;/code&gt; 還是會更值得信任一些，但並不完美。&lt;/p&gt;
&lt;p&gt;回過頭來如果使用的是 &lt;code&gt;did:ethr&lt;/code&gt; 時，當 DID 擁有者更新鑰匙資訊的時候會留下歷史紀錄，並且可以透過 &lt;a href=&quot;https://github.com/decentralized-identity/ethr-did-resolver/blob/master/doc/did-method-spec.md#versionid-query-string-parameter&quot;&gt;參數查詢&lt;/a&gt; 之前版本的 DID 文件，相較於 &lt;code&gt;did:web&lt;/code&gt; 又會更值得信賴一些。&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>筆記歸類：建構自己的偵探證據牆</title><link>https://yurenju.blog/zh/posts/2023-10-28_note-evidence-wall/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2023-10-28_note-evidence-wall/</guid><pubDate>Sat, 28 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;分類牆&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1600&quot; height=&quot;1600&quot; src=&quot;/_astro/cover_note-evidence-wall.CvRNSDFP_2ey3PD.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;你曾經試過用分類（Categories）或是標籤（Tags）來嘗試著分類你的筆記嗎？&lt;/p&gt;
&lt;p&gt;為了想要重新利用與堆疊自己的筆記產生新想法，我通常都會在筆記裡面有一個 “See Also” 的段落，放置自己覺得相關的筆記，並在需要的時候寫上一些註解描述。但當筆記變多之後，要找到相關的筆記就愈來愈困難。&lt;/p&gt;
&lt;p&gt;而不論是發表的文章或是筆記，我都嘗試過用分類或是標籤來試圖找到相關的筆記，但是效果一直都不是很好，標籤與分類的顆粒粗細很難掌握，太細跟太粗都找不到關聯的筆記。&lt;/p&gt;
&lt;p&gt;不過當 Obsidian 筆記軟體發表了新功能&lt;strong&gt;畫布&lt;/strong&gt;（Canvas）後，我發現這是一個找到關聯筆記的優秀解決方案。我感覺我就像是電影裡的偵探一樣，一個個的把筆記用大頭針釘在地圖上，用棉線纏繞住好幾個相關的筆記，哼哼的冷笑兩聲後，眼神銳利的把兇手的照片用紅筆框起來（想太多）。&lt;/p&gt;
&lt;p&gt;以下解釋一下問題脈絡，再來看我怎麼樣用畫布功能來找到相關連的筆記。&lt;/p&gt;
&lt;h2 id=&quot;我的問題&quot;&gt;我的問題&lt;/h2&gt;
&lt;p&gt;我的狀況是大約有一千多篇筆記是這一年多來用固定格式紀錄的筆記，當超過一兩百篇之後就愈來愈難找到關聯了。之所以想要找到筆記之間的關聯，主要是希望可以併發出新的想法，或是找到我寫筆記時沒有注意到的細微知識，但是把兩篇關聯在一起之後就會比較容易發現。&lt;/p&gt;
&lt;p&gt;比如說這篇《原創性與好奇心》筆記底下的 See Also 我會有一些相關的筆記跟註解，寫這些註解的實際用處是讓我在寫註解的時候就&lt;strong&gt;再經過一次思考與咀嚼&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;原創性與好奇心&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1436&quot; height=&quot;1000&quot; src=&quot;/_astro/curiosity-note-screenshot.BHcG0SmG_Z2qP60j.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;但是這樣的關聯通過分類或是標籤不是很容易發現，但有時我自己腦子裡有時候可以想到一些可能有關的筆記填寫進去，這邊反倒是靠著我自己的&lt;strong&gt;聯想&lt;/strong&gt;能力。而其他的部分可以靠關鍵字搜尋或許有機會找到一些。&lt;/p&gt;
&lt;p&gt;那麼 Obsidian 的畫布功能是怎麼幫助我的呢？&lt;/p&gt;
&lt;h2 id=&quot;我怎麼用畫布canvas&quot;&gt;我怎麼用畫布（Canvas）&lt;/h2&gt;
&lt;p&gt;Obsidian 的畫布是一種可以把多篇筆記、圖片放到一個平面上，並且可以有許多不同的整理工具，比如說線段、群組等等。&lt;/p&gt;
&lt;p&gt;而我的用法也很簡單：我把每一篇筆記都放到同一個畫布裡面，並且把&lt;strong&gt;相關的筆記放在一起&lt;/strong&gt;，我的畫布遠遠看是這樣：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;我的筆記畫布&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1750&quot; height=&quot;1000&quot; src=&quot;/_astro/canvas-screenshot.DKyMvVb-_Z1MozVg.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;裡面的每個色塊是一個分類群組，而每個群組都會有一個圖示來讓我在遠遠的鳥瞰時就可以知道他是哪個分類，比如說中間左邊的哭臉是&lt;strong&gt;情緒&lt;/strong&gt;群組，情緒左邊的大腦圖示是&lt;strong&gt;哲學、記憶、思考&lt;/strong&gt;，我的分類通常都不會太精確。&lt;/p&gt;
&lt;p&gt;當我寫完一篇新的筆記時，我就會打開這個畫布，並且就像是在閒逛一樣先鳥瞰整個畫布，想想這篇筆記可能可以歸類在哪裡，然後再放大到我覺得有可能的地方。這時候我可能會放大錯地方，但是也沒關係，因為在這樣閒逛的同時，透過一瞥（At a glance）的瀏覽內容，也會重新讓這些筆記的資訊重新回憶，也有助於我一邊思考與檢索關聯。&lt;/p&gt;
&lt;p&gt;由於畫布一次可以看到很多篇，通常只要有標題跟一些前面的一些文字就有助於我回想起筆記內容，偶爾可以從這樣的一瞥形式的瀏覽找到相關的筆記，特別是自己原本覺得毫不相關的筆記，但仔細想想其實有相關就會被我記錄到 See Also 段落裡面。&lt;/p&gt;
&lt;p&gt;當我找到覺得最相關、最適合存放的位置時，就會把那篇筆記放下去，舉上面那篇《原創性與好奇心》的筆記，他被放置的地方是這裡：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;原創性筆記在畫布的位置&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1688&quot; height=&quot;1100&quot; src=&quot;/_astro/curiosity-note-in-canvas.CWS6Mbr1_Z1p6E4I.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;這個時候旁邊通常都會是跟這篇筆記非常相關的內容，這個時候我也會再閱讀一下旁邊的筆記，並且把相關的筆記放入 See Also 裡面。&lt;/p&gt;
&lt;p&gt;而這樣視覺的歸納法提供了幾個優點。&lt;/p&gt;
&lt;h3 id=&quot;一瞥式的閱覽-at-a-glance-reading&quot;&gt;一瞥式的閱覽 (at a glance reading)&lt;/h3&gt;
&lt;p&gt;就像在逛書店或是看偵探證據牆一樣，當我們接收到這些視覺資訊時，經常可以刺激我們的聯想能力，而且這種攤開每篇筆記閱讀標題以及開頭的部分內文時，我自己確實也可以從這樣的過程中回想起筆記內容，進一步思考關聯性。&lt;/p&gt;
&lt;p&gt;比如說當我閒逛到下圖這一區的時候，標題以及前面露出來的內文就可以讓我快速重新提取資訊並且思考關聯性。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;screenshot-partial-content.png&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1154&quot; height=&quot;862&quot; src=&quot;/_astro/screenshot-partial-content.4_9p5XLv_Z55xWm.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;而在畫布&lt;strong&gt;閒逛&lt;/strong&gt;本身也很重要，有很多有趣的想法都需要這樣&lt;strong&gt;不經意的提起&lt;/strong&gt;。比如說在公司的茶水間閒聊，或是在大家一起吃下午茶或是雞排的時候併發的一些想法。在畫布的閒逛也替代了某種隨機的閱讀，並且透過標題以及開頭內文的輔助來形成快速提取資訊。&lt;/p&gt;
&lt;h3 id=&quot;視覺距離的關聯取代分類&quot;&gt;視覺距離的關聯取代分類&lt;/h3&gt;
&lt;p&gt;跟一般的分類與標籤相較起來，這樣的歸類方式其實是透過&lt;strong&gt;距離&lt;/strong&gt;來評量關聯程度。這樣的歸類法比較不會因為顆粒細度影響相關筆記的檢索能力，同時也不需要太精準，因為我們只要在差不多的距離裡面可以看到筆記就行，畢竟我們的目的是要找到相關的筆記。&lt;/p&gt;
&lt;p&gt;分類或索引的時候，進入到相同的標籤或分類的同時通常會有很多篇筆記，但標題排序、時間排序通常都不會讓我們更容易找到相關連的文章，比較有用的排序方式是&lt;strong&gt;不同筆記之間，相同標籤的多寡&lt;/strong&gt;。但是這樣又需要建立大量標籤時才會逐漸有用，但同時又要面對標籤分類顆粒粗細的問題。&lt;/p&gt;
&lt;p&gt;透過視覺化距離的關聯反而可以聚集這些相關的筆記。&lt;/p&gt;
&lt;h2 id=&quot;使用了一段時間的感想&quot;&gt;使用了一段時間的感想&lt;/h2&gt;
&lt;p&gt;我使用這個方法來建立視覺化的索引方式差不多半年了，這樣的使用經驗確實讓我自己在探索相關筆記有很大的進步，而&lt;strong&gt;聯想&lt;/strong&gt;與&lt;strong&gt;思考&lt;/strong&gt;畢竟是我作筆記最想要達成的目標，那些寫下相關筆記後，接著補充的註解經常給了我很多不同的思考空間，甚至都足以再寫另外一篇筆記來擴展想法。&lt;/p&gt;
&lt;p&gt;分類與標籤帶給我的實質幫助在筆記愈來愈多的情況下就越顯低落，相較之下視覺化索引即使在這種情況下還是很有幫助。&lt;/p&gt;
&lt;p&gt;總之對我來說很有用，但我不知道是不是對每個人都有用，但試試也無妨對吧？至少你會得到一幅很酷的偵探證據關聯牆 🕵️‍♂️&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>Account Abstraction：那些解決沒有 ETH 就不能發起交易的提案們</title><link>https://yurenju.blog/zh/posts/2023-10-23_account-abstraction/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2023-10-23_account-abstraction/</guid><pubDate>Mon, 23 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;手持鈔票卻沒有兌幣機的時候&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1575&quot; height=&quot;900&quot; src=&quot;/_astro/cover_account-abstraction.-c-vgY7r_Z41gym.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;你曾經有 1000 USDC 在 Ethereum 帳號裡面卻因為沒有 ETH 而沒辦法發起交易嗎？&lt;/p&gt;
&lt;p&gt;此 1000 USDC 可以替換成任何一種代幣。需要 ETH 才可以發起交易一直都是 Ethereum 使用體驗上的不便之處，當開始常用 Ethereum 之後幾乎一定會遇到這個惱人的問題。&lt;/p&gt;
&lt;p&gt;而帳號抽象化（Account Abstraction, AA）這個名詞經常隨著上面這個問題一起被提及，而這又涉及到了幾個不同的以太坊改善提案（Ethereum Improvement Proposals, EIPs），不同提案中大量的技術內容則又讓人感到更困擾了。&lt;/p&gt;
&lt;p&gt;本文將會梳理與介紹幾個不同的提案，並且透過同樣的範例來解釋他們分別解決了什麼問題。由於 Account Abstraction 涉及的知識非常多，本文將會把重點放在不同提案之間可以改善哪些使用者體驗，但不會談到太過深入的技術細節。&lt;/p&gt;
&lt;h2 id=&quot;例子&quot;&gt;例子&lt;/h2&gt;
&lt;p&gt;Alice 安裝了錢包軟體，其中錢包軟體幫 Alice 產生了私鑰與相對應的 Ethereum 帳號，這樣擁有私鑰的 Ethereum 帳號稱 EOA (Externally Owned Account)。&lt;/p&gt;
&lt;p&gt;他從朋友 Bob 那邊收到了 1000 USDC，原本想到 MockSwap 交易所上面用 USDC 換得一些 ETH，但是因為沒有 ETH 的關係就無法在 Ethereum 上面發起交易。&lt;/p&gt;
&lt;p&gt;這個時候只能再拜託 Bob 給他一點點 ETH 他才可以開始使用這個帳號。&lt;/p&gt;
&lt;p&gt;接下來我們就用上面這個例子，套用了不同的 EIP 之後使用者體驗可以得到什麼改善。&lt;/p&gt;
&lt;h2 id=&quot;eip-4337-不需要修改-ethereum-協議的解決方案&quot;&gt;EIP-4337: 不需要修改 Ethereum 協議的解決方案&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://eips.ethereum.org/EIPS/eip-4337&quot;&gt;EIP-4337&lt;/a&gt; 是一種透過智能合約以及一個鏈下蒐集交易需求的解決方案，這樣可以在不需要修改 Ethereum 協議的狀況下改善使用者體驗。我們先來用上面的例子來看看透過 EIP-4337 可以得到的效果。&lt;/p&gt;
&lt;h3 id=&quot;eip-4337-套用後的情況&quot;&gt;EIP-4337 套用後的情況&lt;/h3&gt;
&lt;p&gt;如果使用支援 EIP-4337 的錢包軟體時，錢包軟體除了幫 Alice 產生私鑰以外，還會幫他建立一個合約錢包，所以 Bob 轉 1000 USDC 的時候會轉到 Alice 的合約錢包。&lt;/p&gt;
&lt;p&gt;而 Alice 的合約錢包裡面雖然沒有 ETH，但是由於是合約錢包的關係，所以還是可以在 MockSwap 上面用 USDC 換得 ETH，並且可以用 USDC 來支付交易費。&lt;/p&gt;
&lt;h3 id=&quot;解釋&quot;&gt;解釋&lt;/h3&gt;
&lt;p&gt;以上的例子可以感受到使用者體驗已經增進很多了，Alice 即使不需要 ETH 也可以用 USDC 交換到 ETH。錢包軟體會幫 Alice 建立的合約錢包，當 Alice 需要送出交易時，實際上會把他要送出的交易以及他的簽名包裝成一種資料結構 &lt;code&gt;UserOperation&lt;/code&gt;，並且透過錢包軟體提供的 APIs 發送。&lt;/p&gt;
&lt;p&gt;當錢包軟體所提供的 API 收到使用者想進行的交易請求後，會把一個或多個操作請求打包成交易後送出，呼叫合約錢包的函式作為代理，並且在合約錢包裡面驗證使用者的簽名，驗證通過之後透過合約錢包呼叫 MockSwap 的函式進行代幣交換。&lt;/p&gt;
&lt;p&gt;實際上的 ETH 交易費會由錢包軟體所管理的一個 EOA 支出，但在這樣的結構下可以讓使用者用不同的代幣如 USDC 支出，並且一段時間後把 USDC 交換成 ETH 並且存入這個 EOA 帳號。&lt;/p&gt;
&lt;p&gt;而 EIP-4337 提供了更進一步的功能是可以用各種不同的驗證方式，不限於 Ethereum 私鑰所支援的 ECDSA 的 secp256k1 橢圓曲線，這樣可以增加很多彈性。&lt;/p&gt;
&lt;p&gt;比如說它也可以設計成用 TouchID 或是 FaceID 進行 &lt;strong&gt;P-256&lt;/strong&gt; 簽章，由於 iOS 透過硬體保護的私鑰都不會暴露在開發者可以存取的範圍，所以這樣也可以做成既安全又方便的設計。相同的 Android 的生物識別裝置，透過 Android Keystore 也支援 P-256 曲線。&lt;/p&gt;
&lt;p&gt;EIP-4337 目前唯一、同時也是最大的缺點是他需要幫使用者建立合約錢包。建立合約錢包在 Layer 2 以目前的成本來說是可行的，但是如果要在 Ethereum Layer 1 上面建立就要看當下的交易費，以歷史資料來看補助使用者建立 Layer 1 的合約錢包，由於成本波動太高不太實際。&lt;/p&gt;
&lt;h2 id=&quot;eip-3074-原生-eoa-的代付機制&quot;&gt;EIP-3074: 原生 EOA 的代付機制&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://eips.ethereum.org/EIPS/eip-3074&quot;&gt;EIP-3074&lt;/a&gt; 是一個相對的輕量修改方案，透過新增兩個 opcode 來讓 EOA 帳號所需支出的交易費可以由其他的帳號代付，我們用同樣一個例子來看 EIP-3074 可以達成的效果。&lt;/p&gt;
&lt;h3 id=&quot;eip-3074-套用後的情況&quot;&gt;EIP-3074 套用後的情況&lt;/h3&gt;
&lt;p&gt;當 Ethereum 支援 EIP-3074 以後，Alice 可以直接使用他的 EOA 帳號把 1000 USDC 在 MockSwap 交換成 ETH，在這之前他的帳號底下不需要有 ETH 才可以發起交易。&lt;/p&gt;
&lt;h3 id=&quot;解釋-1&quot;&gt;解釋&lt;/h3&gt;
&lt;p&gt;實際上 EIP-3074 新增了兩個新的 opcode:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AUTH&lt;/strong&gt;: 新增一個變數 &lt;code&gt;authorized&lt;/code&gt;，呼叫 AUTH 時將會驗證參數中的 ECDSA 簽名，驗證成功時會將把 &lt;code&gt;authorized&lt;/code&gt; 設定為使用者 EOA 的地址。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AUTHCALL&lt;/strong&gt;: 可以使用 &lt;code&gt;authorized&lt;/code&gt; 變數裡面所指定的 EOA 身分進行交易。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;實際上的作法是 Alice 把自己想要的&lt;strong&gt;操作需求&lt;/strong&gt;用私鑰進行簽章之後透過錢包軟體提供的 API 送出這個有簽章的交易請求，接下來錢包軟體會用他們所管理的一個 EOA 帳號發出交易。&lt;/p&gt;
&lt;p&gt;而執行的內容則是會透過一個代理的智能合約，先透過 &lt;code&gt;AUTH&lt;/code&gt; 驗證使用者的簽章，驗證完成之後再透過 &lt;code&gt;AUTHCALL&lt;/code&gt; 代替使用者的身分發出交易。所以這邊付出 ETH 交易費的會是錢包軟體所管理的 EOA 帳號，但是交易發生時會以 Alice 的帳號發出，這樣就可以達成不需要 ETH 的情況下也可以發出交易。&lt;/p&gt;
&lt;p&gt;這樣的好處是不需要幫每一個使用者都建立一個智能合約錢包，而是可以更彈性的讓使用者用自己的 EOA 接收 USDC，並且支援從 EOA 帳號發出交易而不需要先有 ETH。&lt;/p&gt;
&lt;p&gt;缺點有幾個。首先是會需要升級 Ethereum 協議，相較於 EIP-4337 可以完全用現有架構辦到，新增這兩個 opcode 會需要修改協議。另外一個缺點是 AUTH 所檢查的簽名只能是帳號的 ECDSA 演算法的 &lt;code&gt;secp256k1&lt;/code&gt; 橢圓曲線簽名，比起上個解決方案可以用 FaceID 進行簽章會比較沒有彈性與易用性。&lt;/p&gt;
&lt;h2 id=&quot;eip-2938-完整支援-account-abstraction&quot;&gt;EIP-2938: 完整支援 Account Abstraction&lt;/h2&gt;
&lt;p&gt;這是最早提出的方案，也是一個從修改協議層的角度下完整解決這個問題的改善方案。相同的讓我們先來看一下套用改善方案之後的情境。&lt;/p&gt;
&lt;h3 id=&quot;eip-2938-套用後的情況&quot;&gt;EIP-2938 套用後的情況&lt;/h3&gt;
&lt;p&gt;當 Ethereum 支援 EIP-2938 以後，Alice 可以直接把他 EOA 帳號底下的 USDC 在 MockSwap 交換成 ETH，除了不需要先有 ETH 以外，還可以用 TouchID 或是 FaceID 驗證身分，使用者體驗得到的大幅度的改善。&lt;/p&gt;
&lt;h3 id=&quot;解釋-2&quot;&gt;解釋&lt;/h3&gt;
&lt;p&gt;EIP-2938 大幅度的修改了 Ethereum 協議，使得發送交易時不需要附上原本 Ethereum 原生支援的 &lt;code&gt;secp256k1&lt;/code&gt; 簽章格式，而是可以在智能合約裡面實作自訂的驗證邏輯，並且由於不需要原生簽名的關係，可以直接由智能合約發送交易，同時也新增 PAYGAS opcde 達成讓智能合約支付交易費的功能。&lt;/p&gt;
&lt;p&gt;EIP-2938 修改的非常多，這邊只是非常粗淺的介紹，深入內容有興趣可以閱讀  &lt;a href=&quot;https://medium.com/taipei-ethereum-meetup/account-abstraction-%E6%8A%BD%E8%B1%A1%E5%B8%B3%E6%88%B6-eip-2938-%E7%B0%A1%E4%BB%8B-edfd64fac767&quot;&gt;Account Abstraction 抽象帳戶：EIP-2938 簡介 | by ChiHaoLu&lt;/a&gt; 以及作者發表的一系列 AA 文章。&lt;/p&gt;
&lt;p&gt;EIP-2938 透過 Ethereum 協議的修改完整的解決了使用者體驗的問題，既可以用自訂的驗證方式支援不一樣的密碼學演算法，讓 TouchID 這類安全裝置內的私鑰進行簽章，同時又解開了 Contract Account 不能自行發行交易的限制。&lt;/p&gt;
&lt;p&gt;缺點也顯而易見，EIP-2938 需要大幅度的修改協議，這麼大的修改實際上要充足討論到可以上線的時間顯然是曠日廢時。&lt;/p&gt;
&lt;h2 id=&quot;結論&quot;&gt;結論&lt;/h2&gt;
&lt;p&gt;以上比較了三個不同的改善方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EIP-4337&lt;/strong&gt;：一個智能合約與鏈下設計的解決方案，可以在不修改 Ethereum 的狀況下達成 AA&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EIP-3074&lt;/strong&gt;：讓 EOA 錢包可以透過兩個新的 opcode 達成只需 ECDSA 簽名並且無須 ETH 也可以執行交易&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EIP-2938&lt;/strong&gt;：究極目標，讓 EOA 可以透過自訂驗證方式，無須 ETH 的情況下執行交易&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下是不同的改善方案的優缺點。&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;方案名稱&lt;/th&gt;&lt;th&gt;不需修改協議&lt;/th&gt;&lt;th&gt;不需為每個人建立合約錢包&lt;/th&gt;&lt;th&gt;支援多種簽名方式&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;EIP-4337&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;EIP-3074&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;EIP-2938&lt;/td&gt;&lt;td&gt;❌（大幅度）&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;雖然 EIP-2938 是究極的解決方案，但面對要大幅度修改 Ethereum 協議，真的上線目前還是遙遙無期。EIP-3074 則位於中間地帶，相對 EIP-2938 來說修改幅度較小，但可以有效的解決 EOA 一定要有 ETH 才能送出交易的問題，不過看目前的 &lt;a href=&quot;https://ethereum-magicians.org/t/eip-3074-auth-and-authcall-opcodes/4880/130&quot;&gt;討論狀況&lt;/a&gt; 似乎也沒有太多進展。&lt;/p&gt;
&lt;p&gt;EIP-4337 是最新提出的方案，既可以透過 TouchID/FaceID 來驗證使用者，同時透過合約錢包提供更多方便的功能例如社交還原等等功能，當然也支援了使用不同代幣支付交易費的情境。&lt;/p&gt;
&lt;p&gt;雖然需要幫使用者建立合約錢包在 Ethereum Layer 1 上面成本不太合理，但是在眾多的 Layer 2 解決方案裡面由於交易費低廉，使得 EIP-4337 是一個在 Layer 2 最合理的解決方案。&lt;/p&gt;
&lt;p&gt;Account Abstraction 在台灣社群裡面有非常多文章討論，本文並沒有針對技術細節詳加討論，若有興趣以下有一些推薦閱讀清單，如 &lt;a href=&quot;https://medium.com/@antonassocareer&quot;&gt;Anton&lt;/a&gt; 的 &lt;a href=&quot;https://medium.com/taipei-ethereum-meetup/%E5%BE%9E%E6%8A%BD%E8%B1%A1%E5%B8%B3%E6%88%B6%E5%88%B0erc4337-1b10d417b8d5&quot;&gt;從抽象帳戶到 ERC4337&lt;/a&gt;，另外 &lt;a href=&quot;https://medium.com/@ChiHaoLu&quot;&gt;ChiHaoLu&lt;/a&gt; 有一系列的文章討論 AA 也非常值得一讀，列表如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/taipei-ethereum-meetup/account-abstraction-%E6%8A%BD%E8%B1%A1%E5%B8%B3%E6%88%B6-eip-3074-%E8%88%87-eip-4337-%E7%B0%A1%E4%BB%8B-cb4e1f3f6864&quot;&gt;Account Abstraction 抽象帳戶：EIP-3074 與 EIP-4337 簡介&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/taipei-ethereum-meetup/account-abstraction-%E6%8A%BD%E8%B1%A1%E5%B8%B3%E6%88%B6-eip-2938-%E7%B0%A1%E4%BB%8B-edfd64fac767&quot;&gt;Account Abstraction 抽象帳戶：EIP-2938 簡介&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/imtoken/account-abstraction-%E4%BB%8B%E7%B4%B9-%E4%B8%80-%E4%BB%A5%E5%A4%AA%E5%9D%8A%E7%9A%84%E5%B8%B3%E6%88%B6%E7%8F%BE%E6%B3%81-6c03c303f229&quot;&gt;Account Abstraction 介紹（一）：以太坊的帳戶現況&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/imtoken/account-abstraction-%E4%BB%8B%E7%B4%B9-%E4%BA%8C-%E4%BB%A5%E5%A4%AA%E5%9D%8A%E6%9C%AA%E4%BE%86%E7%9A%84%E5%B8%B3%E6%88%B6%E9%AB%94%E9%A9%97-cca0380d3ba5&quot;&gt;Account Abstraction 介紹（二）：以太坊未來的帳戶體驗&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@ChiHaoLu/2023-%E5%B9%B4%E5%A6%82%E4%BD%95%E6%8E%A5%E8%A7%B8-account-abstraction-b488f8fd6ab&quot;&gt;2023 年如何接觸 Account Abstraction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/taipei-ethereum-meetup/zksync-%E4%B8%AD%E7%9A%84%E5%8E%9F%E7%94%9F-account-abstraction-%E4%BB%8B%E7%B4%B9-bc7269f8893a&quot;&gt;zkSync 中的原生 Account Abstraction 介紹&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>技術</category></item><item><title>Knowledge Night 會後感想</title><link>https://yurenju.blog/zh/posts/2023-09-27_thoughts-in-knowledge-night/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2023-09-27_thoughts-in-knowledge-night/</guid><pubDate>Wed, 27 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img  loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1600&quot; height=&quot;1280&quot; src=&quot;/_astro/cover_do-what-makes-your-soul-happy.yUFFlwkC_2pbDC5.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;這周一在 Knowledge Night 分享了自己目前記筆記的方式與理由，講題結束完跟大家的交流覺得有些有意思的地方，寫在這邊作為延伸思考的一部分。&lt;/p&gt;
&lt;p&gt;首先這次我雖然是分享自己寫知識筆記的方式，但實質上是透過寫筆記這樣的過程來進行比以前要更深度的思考與透過聯想加強自己未來檢索出相關資訊的能力。所以是否作筆記不見得那麼重要，但思考本身很重要。&lt;/p&gt;
&lt;h2 id=&quot;講題&quot;&gt;講題&lt;/h2&gt;
&lt;p&gt;這次的四個演講的交集是&lt;strong&gt;知識管理&lt;/strong&gt;，而大家的講題則是南轅北轍的不同，讓我覺得有機會可以吸收完全不同觀點的想法真的很棒。&lt;/p&gt;
&lt;p&gt;牆壁講的強調是如何賦予大量資料足夠的脈絡，讓這些大量的資料可以在宏觀的脈絡下面產生足夠的有用資訊。&lt;/p&gt;
&lt;p&gt;我則專注在閱讀時能夠進行更深度的思考與聯想。比起我以前閱讀大量的資料，我現在反而閱讀更少的資料，但是讓自己每次閱讀都能夠獲得更多想法。&lt;/p&gt;
&lt;p&gt;Ernest 則是不愧是抽象化的達人，分享了抽象化的思考框架，透過這些思考工具來從更高階的角度來闡述自己在知識管理時用的工具。&lt;/p&gt;
&lt;p&gt;而小畢則是講解了自己嘗試過的筆記工具以及每個筆記工具的優缺點以及他們獨一無二的特點以及標籤分類的困難之處。&lt;/p&gt;
&lt;h2 id=&quot;會後討論&quot;&gt;會後討論&lt;/h2&gt;
&lt;p&gt;布丁提到了張忠謀不作筆記，我馬上想到的是他肯定有透過別的方式思考。回頭查了&lt;a href=&quot;https://money.udn.com/money/story/5612/7285237&quot;&gt;相關新聞&lt;/a&gt;之後確實也是這樣。&lt;/p&gt;
&lt;p&gt;在演講裡面我有提到如果是在閱讀、實作、寫作跟交流討論這四種不一樣的學習行為，我會覺得在認真作筆記以前，&lt;strong&gt;閱讀&lt;/strong&gt;是我學習知識的管道裡面吸收效率最不好的。而透過特定的筆記方式讓自己作筆記的時候就加強思考的深度確實讓我獲得了很多有趣的想法，而張忠謀就媒體上面的報導來說他可能更擅長於閱讀時就思考，或是透過與其他人交流來組織、思考自己學習到的東西。&lt;/p&gt;
&lt;p&gt;後來又聊到&lt;strong&gt;演講學習法&lt;/strong&gt;也是我自己經常作的事情，畢竟分享講題的時候就是會強迫自己搞懂一件事情，當作投影片與練習演講時，很快的就會發現自己不足之處並且在演講之前補足自己還沒搞懂的東西。寫文章或是跟別人討論也是一樣的道理，當你試著講解一件事情的時候，腦袋裡面就會重新排列組合知識，當講出口、完成寫作時知識肯定梳理得比起之前要更加清晰了。&lt;/p&gt;
&lt;p&gt;而小畢提到《卡片盒筆記》以前在沒有數位工具的狀況只能靠抄寫這樣比較低效的複製方式，而現在數位的複製貼上則把複製的成本降到極低，相較起來現代科技在資訊的擷取與傳遞上真的是有神速的進展。確實這也是為什麼我在有筆記型電腦之後一直到現在都是採用數位方式作筆記的原因。&lt;/p&gt;
&lt;p&gt;但是在施行卡片盒筆記一年多後我反而理解了「把螢光標記的重點，重新用自己的話轉述一次寫下來」的優點。看似一個多餘的動作，但是因為需要重新轉寫的關係，讓自己在轉寫重點的當下就產生了一次粗略的思考。&lt;/p&gt;
&lt;p&gt;比如說我正在學習一門新知識，相較於單純把重點複製到筆記 app 裡面，如果可以寫下心得或是歸納出重點時，後者大多都可以很大程度的提升自己對新知識的掌握程度。&lt;/p&gt;
&lt;p&gt;所以重點是在吸收知識時，有什麼方式可以&lt;strong&gt;讓自己重新咀嚼思考這個知識&lt;/strong&gt;。要不要作筆記並不是真正的重點，重點是什麼方式可以促進自己思考。張忠謀不作筆記但是卻勤於閱讀與跟專家請教，布丁的演講學習法都一樣，找到一個好方法可以促進自己的知識重新梳理與深度思考才是最重要的事情。&lt;/p&gt;
&lt;p&gt;希望大家都可以在思考與梳理知識中併發出自己覺得有意思的新想法，就跟照片裡面的標語那樣作一些讓自己的靈魂感到快樂的事情 😎&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>從 Facebook 無端封鎖帳號來看數位身分的問題與 DID 解決方案</title><link>https://yurenju.blog/zh/posts/2023-08-21_fb-ban-and-did-solution/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2023-08-21_fb-ban-and-did-solution/</guid><pubDate>Mon, 21 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;封面照片&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1600&quot; height=&quot;1200&quot; src=&quot;/_astro/cover_did.EPZZl06n_1Q3499.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;四年前我的 Facebook 帳號被官方封鎖了。他們說我的帳號是可疑帳號，而我用盡了一切辦法嘗試著通過驗證或是聯絡官方都沒有用，最終 Facebook 決定永久刪除我的帳號。當自己做的再多努力都沒用，才發現我根本沒有擁有我的帳號，擁有我的帳號的是 Facebook 這個企業，他可以決定這個帳號的去向，並且不需要我的同意。&lt;/p&gt;
&lt;p&gt;但問題不止於此，我意識到透過 Facebook 帳號直接登入的其他服務也不能用了，還好我並不常這樣作，只有少數的服務受到影響。但沿著這個思路想下去，則讓我愈來愈感到不安，如果今天決定封鎖我帳號的不是 Facebook，而是 Google 呢？&lt;/p&gt;
&lt;p&gt;那些無數個透過 Google Account 註冊的帳號該怎麼辦？&lt;/p&gt;
&lt;p&gt;我確實對 Google 充滿了信任感，認為他不會不負責任的封鎖我的帳號 — 而在 Facebook 真的封鎖我帳號之前，我也不覺得這樣的事情會發生在我身上。&lt;/p&gt;
&lt;p&gt;這不禁讓我回想，在現實世界我曾經遺失過證件，雖然造成了一些不便以及需要辦理的手續，但並沒有對我的日常造成巨大的影響，讓生活步步艱難。但是 Facebook 或 Google 帳號如果被封鎖顯然對我會造成非比尋常的影響。&lt;/p&gt;
&lt;p&gt;而經過思索之後，才慢慢理出&lt;strong&gt;身分識別&lt;/strong&gt;在實體世界以及數位世界的差異，以及了解現在有什麼解決方案可以緩解這樣的問題。&lt;/p&gt;
&lt;h2 id=&quot;實體世界&quot;&gt;實體世界&lt;/h2&gt;
&lt;p&gt;在一般的生活當中，&lt;strong&gt;身分&lt;/strong&gt;跟&lt;strong&gt;憑證&lt;/strong&gt;是相關但是不相等的兩件事情。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;身分&lt;/strong&gt; (identity) 是指辨識出「你是誰」的特徵、屬性或是關係。身分可以是一個概略的形式存在，比如說 Alice 走進去超商時，店員單從他「走進超商門口」就辨識出他的&lt;strong&gt;顧客身分&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;顧客與店員&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;2048&quot; height=&quot;1140&quot; src=&quot;/_astro/customer-clerk.CLunBTcA_ZSWYVJ.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;身分也可以是更具體的形式，比如說 Chris 從 Alice 的外觀、講話的聲音跟走路姿勢辨識出了對方是朋友身分的 Alice。&lt;/p&gt;
&lt;p&gt;身分除了個人的自我身分認同以外，當在跟社會中的其他人互動的時候，身分的辨識可以讓雙方知道要怎麼樣進行交流與互動。身分的辨識在實體世界大多情況都是很快就可以完成的。&lt;/p&gt;
&lt;p&gt;而 &lt;strong&gt;憑證&lt;/strong&gt; (Credential) 則是針對一個身分屬性背書的證明文件，比如說身分證是一個為「具備台灣公民身分」這樣屬性所背書的憑證；健身房會員卡是針對「有付費加入會員」的身分所背書的憑證。憑證在特定的場合會用到，比如說使用公家機關的服務，或是使用健身房器材等等。&lt;/p&gt;
&lt;p&gt;根據憑證的性質不同，發行單位通常都需要申請人進行註冊程序與相關驗證，最後才會核發憑證給申請人，比起不需要審核的鄰居、顧客身分，需要註冊、核可程序的憑證持有要花費的心力大得多，會讓人思考這個憑證的重要性與花費的心力相比是否值得。顯然身分證很重要，至於健身房、飲料店的會員卡就要看註冊程序的繁瑣程度與憑證的重要性。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;發行憑證&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1988&quot; height=&quot;1410&quot; src=&quot;/_astro/credential-issuance.DQe7vPZd_2iDHi2.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;日常生活中大多數與人交流都不需要憑證。跟朋友聊天、投錢搭公車、到超商買飲料，這些交流都是自然而然的發生，不會有什麼阻力。當進行人際之間的交流時，相對應的&lt;strong&gt;身分&lt;/strong&gt;會很快速的被識別並且使用，就如同你進入超商，店員會馬上意識到你的顧客身分而進行互動與交流。&lt;/p&gt;
&lt;p&gt;需要使用&lt;strong&gt;憑證&lt;/strong&gt;才能辨識身分的場景，不論是註冊或是使用時使用的阻力都比較大。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;身分與憑證的使用互動&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1988&quot; height=&quot;1410&quot; src=&quot;/_astro/id-credential-interactive.BjDhrVgl_9Vfxv.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;而當憑證遺失時，確實會造成一些麻煩，但卻無礙於日常生活，畢竟大部分狀況，其他人跟你交流的時候，不會要你出示身分證對吧？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;身分&lt;/strong&gt;是自己與其他與你有過互動的人共同交織出來並且在適當的時候帶來足夠的脈絡，讓人與人之間的交流更順暢的資訊。憑證可能會遺失，但是一個人身分遺失的可能性很小，除非認識的所有人都否定你，而且本身也否定自己了，關於「你是誰」這樣的資訊才會慢慢的在這個社會上遺失。&lt;/p&gt;
&lt;p&gt;那數位身分呢？&lt;/p&gt;
&lt;h2 id=&quot;數位世界&quot;&gt;數位世界&lt;/h2&gt;
&lt;p&gt;只有在沒有互動的場合例如閱讀網誌、新聞或是維基百科，才可以在沒有憑證的狀況下進行。否則幾乎所有的數位身分幾乎都跟數位憑證緊緊的綁在一起。&lt;/p&gt;
&lt;p&gt;當你透過 gmail 寄信的時候，你肯定要證明你已經登入了 google 帳號這個&lt;strong&gt;數位憑證&lt;/strong&gt;。就連在 Facebook 按個讚你都需要證明你已經登入 Facebook 了，這些實際上與其他人在網路上互動、溝通的情況，都一定需要驗證數位憑證，雖然勾選「保持登入」可以省下一些麻煩，但背後瀏覽器或是 app 每次都還是會自動的檢查你的憑證是否有效。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;數位世界都需要憑證&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1988&quot; height=&quot;1410&quot; src=&quot;/_astro/digital-interaction.DOtJh6B8_n7fgD.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;所以&lt;strong&gt;數位身分&lt;/strong&gt;跟實體世界的&lt;strong&gt;身分&lt;/strong&gt;很不一樣，實體世界即使不用身分證或會員卡，還是可以在許多不同的場合下與其他人交流與活動。數位身分則幾乎一定需要&lt;strong&gt;數位憑證&lt;/strong&gt;才可以跟其他人互動。實體世界的&lt;strong&gt;身分&lt;/strong&gt;本身是很難遺失或是移除的，但是現況的&lt;strong&gt;數位身分&lt;/strong&gt;卻會跟著&lt;strong&gt;數位憑證&lt;/strong&gt;的移除跟著消失。&lt;/p&gt;
&lt;p&gt;申請數位憑證就跟實體世界申請憑證身分證或是會員卡一樣，申請跟使用的阻力都很大，而且在數位世界只要想要跟其他人互動，就無法避開申請數位憑證的環節。&lt;/p&gt;
&lt;p&gt;由於註冊帳號這件事情實在是太繁瑣，以至於一些大型的服務平台如 Facebook 以及 Google 推出單一帳號登入 (Single Sign-on, SSO) 的服務讓使用者可以大幅簡化註冊程序，只要記住一組主要的帳號密碼就可以利用這個數位憑證在不同的服務上面註冊與使用，這樣的平台提供者稱為身分識別提供商 (Identity Provider, IdP)，也有幾套不同的標準如 &lt;a href=&quot;https://openid.net/developers/how-connect-works/&quot;&gt;OpenID Connect&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;透過 Google 帳號註冊其他服務&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1988&quot; height=&quot;1410&quot; src=&quot;/_astro/openid-connect.4QUjgMX5_Z2quwgn.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;雖然還是要註冊會員帳號，不過透過 Google 會員的這個數位身分以及數位憑證的利用，讓建立與驗證數位憑證變得更簡單了，但是這也代表把自己的數位身分交由 Google 代為管理，自己只是透過帳號密碼來表明自己與那個數位身分的關係。&lt;/p&gt;
&lt;p&gt;而這樣帶來的便利，正好就是數位身分與憑證遇到的問題。當我們使用 SSO 的時候，你對這個 IdP 提供商要很有信心，相信他不會用任何的理由或是藉口來封鎖你的帳號，否則當你的主要數位身分與憑證被撤銷了，所以透過這個憑證登入的服務都會無法使用。如果你大量使用 Facebook 作為你的單一登入帳號，鑑於我親身經歷，建議你快逃。&lt;/p&gt;
&lt;p&gt;而如果為了避免這樣的情況，回到了每個網站都要註冊的狀況，又要重新面臨註冊的繁瑣程度，以及管理無數帳號密碼的窘境。&lt;/p&gt;
&lt;p&gt;有沒有一種方法，可以擁有單一帳號登入的便利，同時可以讓數位身分的控制權掌握在自己手裡，而且又沒有任何缺點呢？&lt;/p&gt;
&lt;p&gt;沒有（笑），但是有幾個企圖打破現狀的 W3C 標準，&lt;a href=&quot;https://www.w3.org/TR/did-core/&quot;&gt;DIDs&lt;/a&gt; 與 &lt;a href=&quot;https://www.w3.org/TR/vc-data-model/&quot;&gt;Verifiable Credentials&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id=&quot;dids-與-verifiable-credentials&quot;&gt;DIDs 與 Verifiable Credentials&lt;/h2&gt;
&lt;p&gt;DIDs 是 Decentralized Identifiers 的縮寫，直譯的話是「去中心化的身分識別方式」，一般會簡稱為&lt;strong&gt;去中心化識別&lt;/strong&gt;。它是一個 Web 標準制定組織 W3C 所提出來的&lt;strong&gt;數位身分識別&lt;/strong&gt;的標準，主要的目的是讓使用者、發行機構等自行持有一個密碼學的鑰匙對，把公鑰放在公開的註冊表，並且透過私鑰進行簽章來作身分識別，而第三方服務可以透過驗證簽章來確認使用者是不是真的擁有這個鑰匙的所有權。&lt;/p&gt;
&lt;p&gt;而 Verifiable Credentials 則是另外一個 W3C 所制定用來規範 &lt;strong&gt;數位憑證&lt;/strong&gt; 的標準。當使用者的 DID 經過驗證後，發行方可以簽署並發行一個數位憑證給使用者來為他的特定屬性來背書，同樣也是用密碼學的方式來簽署要背書的屬性內容。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;DID 與憑證拆開的狀況&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1988&quot; height=&quot;1410&quot; src=&quot;/_astro/did.Dqx3a7Iy_1wsTOL.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;如上圖所示，原本數位身分與數位憑證都是綁在一起，但藉由引進新的元件 DID 之後可以把主要數位身分與註冊不同平台所需要的數位憑證抽離，並且透過 Verifiable Credential 標準協議的數位憑證進行驗證，將身分與憑證抽離開來，更接近實體世界身分與憑證是分開的概念。&lt;/p&gt;
&lt;p&gt;透過這兩個標準，往後的數位身分認證可以透過類似 WebAuthn / passkey 這樣的無密碼登入體驗，同時具備了單一帳號登入 (SSO) 簡化登入流程的特性，並且對於自己的數位身分有完整的控制權，不會再有因為企業封鎖你的帳號，導致許多不同服務都同時無法使用。&lt;/p&gt;
&lt;h2 id=&quot;did-是解決數位身分的銀彈嗎&quot;&gt;DID 是解決數位身分的銀彈嗎？&lt;/h2&gt;
&lt;p&gt;大家都希望有一個完美的解法可以解決所有問題，可惜 DID 並不是銀彈。當使用者透過 DID 擁有了自己數位身分的完整掌控權之後，其他的問題就浮現了：如果密鑰遺失了，誰可以幫我找回來呢？&lt;/p&gt;
&lt;p&gt;這是一個難題。但受益於 DID 標準裡面可接受多密鑰管理單一 DID 的架構設計，可以用不同的機制來解決這個問題，比如說 multisig 多把密鑰簽署或是 Social Recovery 讓認識的朋友或家人協助恢復 DID 數位身分等等。&lt;/p&gt;
&lt;p&gt;然而這些技術雖然都已經存在，但由於 DID 相關的標準協議部分還在制定中，另外這些技術也並未完全整合在一起。要到蒐集到足夠的拼圖，可以把整個全貌拼出來的情況，還會需要生態系逐漸的建構。&lt;/p&gt;
&lt;p&gt;DID 雖然不是那個馬上就可以解決所有問題的銀彈，但是這幾個由 W3C 所制定的相關標準，確實為目前由大企業掌控多數人的數位身分的狀況，產生了一個可以打破現況的契機。&lt;/p&gt;
&lt;p&gt;自從 Facebook 被官方標示為可疑帳號並且永久刪除後，我比以往更加注重數位身分的安全，不再透過單一帳號登入，改用密碼管理軟體為每個服務建立新帳號，也不再太過依賴特定社群網站，而你現在看到這篇文章的網站也是同樣的思路，為了不過度依賴文章發布平台 Medium，而採取自行架設網誌平台。這也讓我在學習 DID 之後，認知到了這個技術往後有可能會改變未來數位世界的身分辨識方式，雖然這樣的標準還在很早期的狀況，但確實是一線曙光。&lt;/p&gt;
&lt;p&gt;如果你也希望擁有一個更加自主的數位生活，歡迎你跟我們一起關心這些技術的進展 😎&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>Blog 遷移經驗談：你也需要自架網誌平台嗎？</title><link>https://yurenju.blog/zh/posts/2023-07-16_blog-migration-experience/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2023-07-16_blog-migration-experience/</guid><pubDate>Sun, 16 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;封面照片&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1600&quot; height=&quot;1599&quot; src=&quot;/_astro/cover_hugo-sharing.C9dwCrrN_Z1n7lpF.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;你也想要從 Medium 離開，自行架設網誌嗎？&lt;/p&gt;
&lt;p&gt;Blogger, Medium 甚至 Facebook 可以說是一個&lt;strong&gt;意見與想法的託管平台&lt;/strong&gt;，它們提供了良好的介面以及大量的功能，讓作者與讀者可以輕鬆的在這個託管平台發表以及閱讀各種資訊與意見。之所以黏著的最大原因，除了那是朋友與社群所在地的網路效應外，主要就是他們提供了許多方便的功能讓作者發表文章的阻力可以降得很低，比如說貼文會自動出現在其他人的首頁上面，或是讀者評論留言功能與點讚、良好的發文介面等等。&lt;/p&gt;
&lt;p&gt;這些都是吸引作者在託管平台發表文章的理由。&lt;/p&gt;
&lt;p&gt;而提供豐富功能的背後，平台方希望的是更多人&lt;strong&gt;消費自己的注意力&lt;/strong&gt;在他們的平台，並且透過注意力來產生收益，比如說 Facebook 靠廣告，而 Medium 則靠付費牆背後的文章。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;託管 vs 自架&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1501&quot; height=&quot;1500&quot; src=&quot;/_astro/hosted-vs-self-host.BiRHLcWv_Gs1Eh.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;當決定要離開託管平台，到一個自己更能夠掌控的自行管理平台時，這些託管平台好處就再也享受不到了。在取得更多權力的同時，也會流失掉許多自己早就已經習慣到沒感覺的優良功能。&lt;/p&gt;
&lt;p&gt;你準備好要接受這些你將會失去的東西了嗎？&lt;/p&gt;
&lt;p&gt;本篇文章會分享自己從 Medium 遷移到 Hugo 這樣的靜態網站解決方案的一些思路，提供給想要搬離託管平台的讀者一個參考。當然每個人都有自己的理由，不管是待在哪裡，能了解自己的需求才是最重要的。如果看完文章你反而覺得 Medium 是一個讓你感到舒適的平台，那也是個很不錯的收穫。&lt;/p&gt;
&lt;h2 id=&quot;引子&quot;&gt;引子&lt;/h2&gt;
&lt;p&gt;對於從 Medium 搬家已經是我思考很久的事情了，時不時也會跟朋友提起這件事情。還記得 &lt;a href=&quot;https://blog.timdream.org/&quot;&gt;timdream&lt;/a&gt; 曾建議我考慮用 Wordpress 的付費方案來節省一點力氣。會一直有搬家的念頭，主要還是 Medium 的利益開始跟身為作者的我越來越不一致開始。&lt;/p&gt;
&lt;p&gt;對我來說 Medium 想方設法營利的「付費」功能反而是我最想離開的主因。一開始的一切都很美好，Medium 針對閱讀與撰寫上做了很多最佳化，所以整體閱讀體驗一直很棒，而平台上面也有許多優質的文章。身為平台的一員，在上面撰寫是一件很享受的事情。&lt;/p&gt;
&lt;p&gt;不過從付費牆開始實施後一切都變得不太一樣了，Medium 開始用一段誘人的序文來引導讀者想要繼續閱讀，而接下來則是一道龐大的付費牆擋在前面，這樣資訊探索的斷裂讓我感到厭煩。那樣的屏蔽就像是情緒勒索一樣的讓人不舒服。我買過一些付費課程，也曾經是科技島讀的讀者，那些付費所得到的知識總是令人感到珍惜。但 Medium 的這種作法特別令人不悅，有種來自道德制高點的勒索感。&lt;/p&gt;
&lt;p&gt;如果你是一個特定作者或是媒體的忠實讀者，優良的付費閱讀通常都是一件令人心服口服又滿載收穫的體驗。而從搜尋網站上面找到一篇看似可以解決你問題的文章，讀了一段之後就要求你付錢的體驗就不太好。在跟作者還沒有建立信賴關係以前，這樣的付費機制讓我沒辦法提起勁掏出錢來。&lt;/p&gt;
&lt;p&gt;從我的角度來說，我更喜歡透過我的文章幫助到一些人，而這些被建立起來的信賴感讓我自己在專業工作中可以獲益，這樣非直接的受益方式更適合我，甚至是一個付費專欄作者也是不錯的選項，但 Medium 的付費體驗就不適合我。當自己總是被平台惹怒，而這個功能完全沒帶給我任何好處時，這就變成累積著離開的導火線了。&lt;/p&gt;
&lt;h2 id=&quot;認真思考搬家的可能&quot;&gt;認真思考搬家的可能&lt;/h2&gt;
&lt;p&gt;但是認真思考之後就會開始被自己勸退。為什麼用 Medium 是有原因的。平台有基礎的流量分析工具，有良好的撰寫介面，同時有各種互動留言功能，當自己要自己管理這個平台時，這樣的功能就需要花費一些功夫才可以做到。&lt;/p&gt;
&lt;p&gt;採用 Wordpress 可以解決上述的一些問題，因為他是一個有後台機制的撰寫平台，流量分析、留言等等都可以輕鬆的處理。但經過嘗試之後發現不太適合我，他強大的功能反而讓我有點難以進入。跟遠古時代的 Wordpress 不同，現在的 Wordpress 有超多功能，而這些功能都不是我會用到的。而他的撰寫介面使用起來也有諸多格格不入。&lt;/p&gt;
&lt;p&gt;所以遷移到 Wordpress 的念頭就慢慢的打消了，但如果要採用靜態網站產生器，就會帶來許多好處 —— 與問題。&lt;/p&gt;
&lt;h2 id=&quot;靜態網站產生器-static-website-generator&quot;&gt;靜態網站產生器 (Static Website Generator)&lt;/h2&gt;
&lt;p&gt;這是一個非常古老的技術。相對於 Wordpress 這樣動態產生內容的網站，靜態網站產生器在每次文章有更新的時候，重新建構（或是快取）所有靜態內容如 HTML, Javascript 以及圖片等資源來構築成一個網站。&lt;/p&gt;
&lt;p&gt;這跟託管平台是南轅北轍的概念，託管平台提供了許多功能與社群，希望使用者盡可能的花更多時間在平台上面，讓他們可以銷售廣告給廣告主來營利。而靜態網站則是每個人都有自己獨立的網站，內容與內容之間的串連則靠更開放通用的協定（比如說，超連結 😎）。&lt;/p&gt;
&lt;p&gt;靜態網站的好處跟壞處都是同一個，就是「靜態」。&lt;/p&gt;
&lt;p&gt;靜態網站的好處是會擁有一個原始的文章來源格式，對我來說就是 Markdown 格式的文章，靜態網站產生器會將這些 markdown 格式的文章在更新的時候全部產生成靜態網站的內容並且佈署到指定的網域，讀取速度快，結構簡單。&lt;/p&gt;
&lt;p&gt;壞處是，他是&lt;strong&gt;靜態&lt;/strong&gt;的。凡舉需要一些互動的地方，比如說讀者留言，靜態網站因為沒有背後動態管理內容的能力，會需要透過一些擴充套件的方式來達成，一個常見的服務是 &lt;a href=&quot;https://blog.disqus.com/&quot;&gt;Disqus&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;發表文章的流程也會變得很不一樣，不像是 Medium 或是 Wordpress 這樣的動態管理系統會有一個可以發表文章、插入圖片的介面，靜態網站會需要直接撰寫 Markdown 檔案，管理要附加的圖片等等，發表文章的流程相對於原本的發表形式會有許多不一樣的地方。&lt;/p&gt;
&lt;p&gt;但是這個需要用開放文件格式撰寫的壞處反而成為我最後選擇靜態網站產生器的原因。&lt;/p&gt;
&lt;h2 id=&quot;文章發布流程&quot;&gt;文章發布流程&lt;/h2&gt;
&lt;p&gt;對我來說發表文章是一種知識的熟成過程。剛開始會是別人脈絡底下的知識，作者講解給如我這樣的讀者聽，從他的角度出發這個知識片段的樣子。當我閱讀並且理解後，它會變成屬於我自己理解的知識片段。&lt;/p&gt;
&lt;p&gt;最後當相關知識匯集到足夠發表成一篇文章後，我把這個知識片段與其他相關知識組合在一起，形成了一篇在讀者脈絡下專門給他們閱讀的文章。到這邊我會覺得這樣的一個知識片段已經在我腦袋已經足夠成熟了。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;知識的熟成&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;1501&quot; height=&quot;1106&quot; src=&quot;/_astro/knowledge-aging.BpOmpfyd_Z1RKLzy.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;而這樣的流程藉由這陣子更積極的撰寫筆記來理解各種知識，在我自己規劃的知識整理流程下，開始在筆記軟體 Obsidian 逐漸累積了許多各式各樣的筆記。在發表文章時，其實我會在 Obsidian 裡面先用 Markdown 寫好要發表的文章內容，再貼到 Medium 的發表文章介面。&lt;/p&gt;
&lt;p&gt;所以對我來說發表文章到 Medium 還是會需要經過一次的轉換程序，把 Markdown 格式的文章貼到 Medium 去。如果發表平台也是 Markdown 格式，反而可以減低我發表文章的阻力。&lt;/p&gt;
&lt;p&gt;這也是為什麼對我來說是個好時機轉換到一個以 Markdown 文件格式為主的靜態網站平台，這樣一來串接這些脈絡的就都是 Markdown 這樣的通用文件格式了。&lt;/p&gt;
&lt;h2 id=&quot;hexo-docusaurus-and-hugo-要怎麼選擇&quot;&gt;Hexo, Docusaurus and Hugo 要怎麼選擇？&lt;/h2&gt;
&lt;p&gt;要用哪種靜態網站產生器是個滿困擾的問題，有太多選擇反而不太容易抉擇。我覺得可以看自己熟悉什麼程式語言來決定會比較容易。我自己是熟悉 JavaScript，所以 Hexo 或是 Docusaurus 會是比較容易的選擇。&lt;/p&gt;
&lt;p&gt;Hexo 比較嚴重的問題是他的官方網站充斥著廣告，讓我感到沒有安全感，充斥著廣告的頁面實在很難讓人安心使用。而 Docusaurus 我自己的使用經驗是想要客製化時並不是那麼容易，Docusaurus 所架設的網站通常會有非常相近的風格，我自己的修改經驗確實覺得也不是很容易修改，所以也沒有選擇使用 Docusaurus。&lt;/p&gt;
&lt;p&gt;Hugo 從我的角度來看是生態系比較健全的平台，對我來說的缺點是自己對於 golang template 掌握度沒有很高，但是看到採用 Hugo 的網站有很多不同的樣態也讓我覺得他的客製化應該是相對容易進行的，同時建構速度也很快。&lt;/p&gt;
&lt;p&gt;開始架設後，確實 golang template 帶給我一些痛苦沒錯。不過寫了幾次 template 之後也比較搞清楚要怎麼用了，如果要進行一些簡單的版面調整也都不是問題，一些 RSS 的問題也參考其他人的分享改好了。&lt;/p&gt;
&lt;p&gt;另外 &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; 的使用者也非常多，如果本身是使用 Ruby 語言的人也可以選擇這套，但因為語言熟悉程度的關係我就沒有選它了。&lt;/p&gt;
&lt;h2 id=&quot;雜項工具&quot;&gt;雜項工具&lt;/h2&gt;
&lt;p&gt;如果你也決定從 medium 搬家，可以使用 &lt;a href=&quot;https://github.com/bgadrian/medium-to-hugo&quot;&gt;bgadrian/medium-to-hugo&lt;/a&gt; 一次性的把所有文章都匯出成適合 Hugo 的 Markdown 格式與目錄結構。&lt;/p&gt;
&lt;p&gt;不過就我自己的經驗是還需要再額外調整一下格式，不過資料上如果都轉移好了，其實打開 vscode 很快的就可以把需要調整的地方調整完畢。&lt;/p&gt;
&lt;p&gt;如果你是從 blogger 搬家，我有寫了一個簡單的腳本來把所有文章跟圖片都搬成 hugo 的形式，這個腳本如果要改成通用版本還會需要花點時間，所以我就不修改了。有需要的話可以從&lt;a href=&quot;https://gist.github.com/yurenju/5c7ff1d9bd090ec6fecf9575d9d05181&quot;&gt;這邊&lt;/a&gt;看一下怎麼寫的，然後改成你自己需要的樣子。&lt;/p&gt;
&lt;p&gt;另外因為可以客製化佈景主題的關係，有些佈景主題不見得會好好利用 Hugo 已經寫好的內建功能，或是有些功能 Hugo 內建的運作方式不太合自己的使用，所以有時候會需要修改一下。比如說最近把 RSS 全文顯示的功能加上去，其實就是&lt;a href=&quot;https://github.com/yurenju/blog/commit/7936dda4a4415288e63b36bd682c3703f100efd0&quot;&gt;修改內建的 RSS 樣板&lt;/a&gt;，我有三個專欄的需求，所以也自己&lt;a href=&quot;https://github.com/yurenju/blog/commit/0ef1d7171b63a754ed47874d3f4fb99134a5031d&quot;&gt;修改了佈景主題&lt;/a&gt;來達成。&lt;/p&gt;
&lt;h2 id=&quot;最重要的問題你需要什麼回饋&quot;&gt;最重要的問題：你需要什麼回饋？&lt;/h2&gt;
&lt;p&gt;在任何長期習慣的培養，有適當的回饋可以更容易讓一件事情持之以恆的前進。寫文章也一樣，如果寫文章可以獲得一些回饋，不論是自己寫完文章心中的滿足感，或是讀者來信講述這些知識真的對他產生幫助，這些回饋都可以支撐著自己繼續保持相同的步調繼續往前。&lt;/p&gt;
&lt;p&gt;當自己架設了靜態網站的網誌後，你就像是網際網路上面的獨立個體了，Facebook 或Medium 沒有理由幫你推廣你的內容。&lt;/p&gt;
&lt;p&gt;那你寫文章需要怎樣的回饋呢？發表文章的滿足感可以驅動你前行嗎？那些原本從網路效應來的流量如果是你珍惜的回饋之一，自架平台後你該如何獲得足夠的動力呢？&lt;/p&gt;
&lt;p&gt;這是個很重要也很容易被忽略的問題。最好的情況是寫文章與發布本身就帶給你足夠的滿足感，你就可以直接在這樣的成果之中享受到這樣的回饋。&lt;/p&gt;
&lt;p&gt;如果不是的話，請好好的思考你所需要的回饋，並且在你自建的這個網誌平台之中，設法撒下這些回饋的種子，並取得你最需要的那種正向回饋感 🔄 。&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>NestJS 實作 EIP-4361 Ethereum 帳號登入機制</title><link>https://yurenju.blog/zh/posts/2023-02-13_nestjs-%E5%AF%A6%E4%BD%9C-eip4361-ethereum-%E5%B8%B3%E8%99%9F%E7%99%BB%E5%85%A5%E6%A9%9F%E5%88%B6/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2023-02-13_nestjs-%E5%AF%A6%E4%BD%9C-eip4361-ethereum-%E5%B8%B3%E8%99%9F%E7%99%BB%E5%85%A5%E6%A9%9F%E5%88%B6/</guid><pubDate>Mon, 13 Feb 2023 01:32:31 GMT</pubDate><content:encoded>&lt;p&gt;帳號註冊與登入以前大多都是透過電子信箱與密碼註冊，而近幾年來則逐漸被如 Google, Facebook 這樣的帳號提供商取代，使用者會想使用第三方登入無非就是因為可以只記住單一密碼的便利性而採用。&lt;/p&gt;
&lt;p&gt;不過近來 Facebook 無端的停用使用者帳號（我也是個受害者），導致透過 FB 帳號註冊的網站帳號都一併無法使用。這樣的情況也會讓人重新思考把擁有、管理帳號的權力交給一個大企業是否合宜，而又有哪些其他解決方案可以保持管理帳號的便利性與不被其他人箝制權力之間取得一個平衡。&lt;/p&gt;
&lt;p&gt;Sign-in with Ethereum (SIWE) 是一個透過 Ethereum 區塊鏈基礎建設的角度思考的解決方案。由於在 Ethereum 上面每個使用者都會擁有自己的公私鑰來管理資產、執行交易，如果可以直接使用這把鑰匙來作為登入的憑證，只要使用者證明自己是一個 Ethereum 帳號的持有者就可以進行註冊與登入，這樣既可以把擁有帳號的權力控制在自己手上，同時又可以取得單一登入方式的便利性。&lt;/p&gt;
&lt;p&gt;以下將會介紹 SIWE 的基礎原理以及如何透過 NestJS 來讓後端應用程序整合 SIWE 登入機制。NestJS 是一套相當強大的後端應用程序框架，有興趣學習的可以參閱 &lt;a href=&quot;https://docs.nestjs.com/&quot;&gt;NestJS 官方文件&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&quot;eip-4361-siwe&quot;&gt;EIP-4361 SIWE&lt;/h3&gt;
&lt;p&gt;Ethereum Foundation 與 ENS (Ethereum Naming Service) 提案了 &lt;a href=&quot;https://eips.ethereum.org/EIPS/eip-4361&quot;&gt;EIP-4361: Sign-In with Ethereum&lt;/a&gt; 作為採用 Ethereum 基礎建設的登入機制，需要透過 Ethereum 登入的服務，只要請求使用者簽署一個結構化的純文字訊息即可登入：&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;${domain} wants you to sign in with your Ethereum account:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;${address}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;${statement}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;URI: ${uri}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Version: ${version}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Chain ID: ${chain-id}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Nonce: ${nonce}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Issued At: ${issued-at}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;當使用者簽章傳回後，只需要檢查傳來的訊息以及簽章是否確實由特定 Ethereum 帳號所簽署就可以確認登入操作是否合法，由於只有簽署者擁有該私鑰才有可能簽署出對應的簽章資訊，所以用這個方法就可以實作登入機制。&lt;/p&gt;
&lt;p&gt;而整個流程當中比較重要的是 &lt;code&gt;nonce&lt;/code&gt; 欄位，由於簽署後的訊息如果被中間人攔截之後就可以再次登入該服務，為了避免這樣的重送攻擊，請求登入的服務會需要實作一個額外的端點取得 &lt;code&gt;nonce&lt;/code&gt; 亂數字串並且紀錄在系統當中，當使用者簽署登入訊息時就像上面的範例訊息一樣要把 &lt;code&gt;nonce&lt;/code&gt; 附在其中，在使用者登入後把這個 &lt;code&gt;nonce&lt;/code&gt; 標記成已使用或是刪除，如此一來這個簽署後的訊息就不能再次拿來登入使用。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;800&quot; height=&quot;468&quot; src=&quot;/_astro/1.UJebAde5_kLhNr.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;上面的流程圖當中 Attacker 即使拿到了使用者原本拿來登入的訊息，因為原本的那個 &lt;code&gt;nonce&lt;/code&gt; 已經被系統刪除，所以即使取得原始訊息也無法再次拿來登入網站。&lt;/p&gt;
&lt;p&gt;所以如果要在一個服務實作 SIWE 登入，我們會需要實作這兩個路由端點：&lt;code&gt;/challenge&lt;/code&gt; 與 &lt;code&gt;/login&lt;/code&gt;，以下使用 NestJS 作為實作範例。&lt;/p&gt;
&lt;h3 id=&quot;實作概觀&quot;&gt;實作概觀&lt;/h3&gt;
&lt;p&gt;NestJS 是一個 Node.js 與 TypeScript 的後端框架解決方案，它提供了 Dependency Injection (DI) 的生命週期管理方式，同時提供許多後端應用程序經常會需要用到的工具如 Guard 作為權限檢查、裝飾子 (Decorators) 來自訂不同功能。&lt;/p&gt;
&lt;p&gt;對我來說最重要的功能是他的 DI 容器讓撰寫測試時模擬 (Mocking) 相依性變得非常直覺好用，讓測試好寫不少。&lt;/p&gt;
&lt;p&gt;NestJS 也提供了與 Passport 驗證框架的整合。Passport 是一個專門用於登錄驗證的框架，提供相對簡單的介面接口讓開發者實作不同的登入機制（Passport 把每個登入機制稱之為策略 Strategy）。從最簡單的帳號密碼登入、JWT 支援、Google 帳號登入都已經支援。而 SIWE 登入策略也由 Passport 的主要開發者 Jared Hanson 撰寫完畢，只需要透過 &lt;code&gt;@nestjs/passport&lt;/code&gt; 就可以把該策略整合到 NestJS 撰寫的應用程序當中。&lt;/p&gt;
&lt;p&gt;接下來我們會實作一個簡易的 NestJS SIWE 登入機制範例，透過整合 Passport 以及 &lt;code&gt;passport-ethereum-siwe&lt;/code&gt; 套件的的策略來達成。&lt;/p&gt;
&lt;h3 id=&quot;nestjs-實作&quot;&gt;NestJS 實作&lt;/h3&gt;
&lt;p&gt;NestJS 提供了 &lt;code&gt;@nestjs/passport&lt;/code&gt; 套件將 passport 策略整合入 NestJS 後端應用程序裡面。要引入一個 Passport 的策略，首先需要建立一個繼承自 &lt;code&gt;PassportStrategy&lt;/code&gt; 類別。&lt;/p&gt;
&lt;p&gt;比如說要整合 &lt;code&gt;passport-google-oidc&lt;/code&gt; 來實現 Google 帳號登入會需要建立一個 &lt;code&gt;GoogleStrategy&lt;/code&gt; 類別繼承自 &lt;code&gt;PassportStrategy&lt;/code&gt; 並且指定採用 &lt;code&gt;passport-google-oidc&lt;/code&gt; 策略，整體運作的流程圖如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;800&quot; height=&quot;633&quot; src=&quot;/_astro/2.B7ZNPlu__Z1EKCBD.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;當使用者發出請求到 &lt;code&gt;/login&lt;/code&gt; 端點後 NestJS 就會觸發 &lt;code&gt;passport-google-oidc&lt;/code&gt; 的 &lt;code&gt;authenticate()&lt;/code&gt; 函式並且開始進行 Google 帳號認證，當完成後會重導到原本網站的 &lt;code&gt;/redirect&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;GoogleStrategy&lt;/code&gt;僅是一個非常輕量的類別，主要的用途是提供一個 callback function &lt;code&gt;validate()&lt;/code&gt;，讓 &lt;code&gt;passport-google-oidc&lt;/code&gt; 在登入成功後重導使用者到 &lt;code&gt;/redirect&lt;/code&gt; 時把資訊帶回的 callback，裡面會帶回 issuer 以及 profile 兩樣資訊。&lt;/p&gt;
&lt;p&gt;在 Controller 端也只要使用 &lt;code&gt;@UseGuard()&lt;/code&gt; 裝飾子與 &lt;code&gt;@nestjs/passport&lt;/code&gt; 提供的 &lt;code&gt;AuthGuard()&lt;/code&gt; 就可以在 &lt;code&gt;/login&lt;/code&gt; 與 &lt;code&gt;/redirect&lt;/code&gt;整端點合 Google 帳號登入，整體概念程式碼約略如下：&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#758575DD&quot;&gt;// google.strategy.ts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; Strategy&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;passport-google-oidc&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; PassportStrategy&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;@nestjs/passport&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; Injectable&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; UnauthorizedException&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;@nestjs/common&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; AuthService&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;./auth.service&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Injectable&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#5DA994&quot;&gt; GoogleStrategy&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; PassportStrategy&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;Strategy&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  async&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; validate&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt;issuer&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt; profile&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#5DA994&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#5DA994&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;profile&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D;--shiki-dark:#758575DD&quot;&gt;// auth.controller.ts&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Controller&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;auth&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#5DA994&quot;&gt; AuthController&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  @&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;login&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  @&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;UseGuards&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;AuthGuard&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;google&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;  login&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(@&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Req&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt; req&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; req&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  @&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;redirect&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  @&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;UseGuards&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;AuthGuard&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;google&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  @&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Redirect&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;  redirect&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要整合 SIWE 也是非常類似的方式，不同之處在於我們會需要在一個 Nonce Store 裡面產生與儲存亂數字串，並且於登入時檢查該亂數是否由系統發出並且使用完畢後刪除，避免攻擊者的重用攻擊：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;800&quot; height=&quot;557&quot; src=&quot;/_astro/3.B6NNFXDe_Z1abWxH.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;跟 &lt;code&gt;GoogleStrategy&lt;/code&gt; 一樣我們會先需要實作一個 &lt;code&gt;EthereumStrategy&lt;/code&gt;，並且在建構子內宣告 &lt;code&gt;SessionNonceStore&lt;/code&gt; 作為 nonce store 用途，另外新增一個 &lt;code&gt;challenge()&lt;/code&gt; 準備來承接 &lt;code&gt;/challenge&lt;/code&gt; POST 端點傳遞過來的 request 物件，並且透過 &lt;code&gt;store.challenge()&lt;/code&gt; 新增一筆 nonce 亂數字串回傳給 &lt;code&gt;/challenge&lt;/code&gt; 端點。&lt;/p&gt;
&lt;p&gt;至於 &lt;code&gt;validate()&lt;/code&gt; 函式會在 &lt;code&gt;passport-ethereum-siwe&lt;/code&gt; 驗證登入訊息成功後作為 callback 函式被呼叫到，其中包含 &lt;code&gt;address&lt;/code&gt; 資訊。&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; Injectable&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;@nestjs/common&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; PassportStrategy&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;@nestjs/passport&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; Request&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; Strategy&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; SessionNonceStore&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;passport-ethereum-siwe&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Injectable&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#5DA994&quot;&gt; EthereumStrategy&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; extends&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; PassportStrategy&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;Strategy&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  private&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt; store&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;    const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; store&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; SessionNonceStore&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#C99076&quot;&gt;    super&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;store&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#C99076&quot;&gt;    this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;store&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; store&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  async&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; validate&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#5DA994&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#5DA994&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#5DA994&quot;&gt;any&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;address&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;  challenge&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#5DA994&quot;&gt; Request&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#B8A965&quot;&gt; Promise&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt;resolve&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt; reject&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#C99076&quot;&gt;      this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;store&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;challenge&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt; nonce&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;          return&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; reject&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;err&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;        }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; else&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;          return&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; resolve&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;({ &lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;nonce&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下來在 Controller 上面把 &lt;code&gt;/challenge&lt;/code&gt; 以及 &lt;code&gt;/login&lt;/code&gt; 兩個 POST 端點加入即可：&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;typescript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; Controller&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; Post&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; Req&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; UseGuards&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;@nestjs/common&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; AuthGuard&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;@nestjs/passport&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; Request&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;express&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; EthereumStrategy&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;./ethereum.strategy&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;@&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Controller&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;auth&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;export&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#5DA994&quot;&gt; AuthController&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  constructor&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt; ethereumStrategy&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#5DA994&quot;&gt; EthereumStrategy&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  @&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Post&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;login&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  @&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;UseGuards&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;AuthGuard&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;ethereum&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;  login&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(@&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Req&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt; req&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; req&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;user&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  @&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Post&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;challenge&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;  challenge&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(@&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;Req&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt; req&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#5DA994&quot;&gt; Request&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#C99076&quot;&gt; this&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;ethereumStrategy&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;challenge&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;req&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在前端實作上需要先透過 &lt;code&gt;/challenge&lt;/code&gt; 取得 nonce 亂數字串，接著透過 &lt;code&gt;siwe&lt;/code&gt; 套件建構出符合 EIP-4361 標準的結構化訊息，再透過 Wallet Provider 如 MetaMask 提供的 &lt;code&gt;personal_sign&lt;/code&gt; 簽名，最後送出 POST 請求到 &lt;code&gt;/login&lt;/code&gt; 端點就可以完成登入。&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; ethers&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;ethers&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt;import&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; SiweMessage&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; from&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;siwe&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; function&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; login&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;()&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; options&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt; method&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;POST&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; url&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;/api/auth/challenge&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; nonce&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;url&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; options&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; res&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; address&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; ethers&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;utils&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;getAddress&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;account&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; rawMessage&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; SiweMessage&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;    domain&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; window&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;location&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;host&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;    address&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; address&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;    statement&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;Sign in with Ethereum to the app.&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;    uri&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; window&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;location&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;origin&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;    version&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;    chainId&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;    nonce&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; message&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; rawMessage&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;prepareMessage&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; signature&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; ethereum&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;    method&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;personal_sign&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;    params&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; address&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#CB7676&quot;&gt;  const&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#4D9375&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt; fetch&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;/api/auth/login&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;    method&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;POST&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;    headers&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;      &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;Content-Type&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;application/json&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;      Accept&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt; &apos;&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D&quot;&gt;application/json&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-dark:#C98A7D77&quot;&gt;&apos;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#B8A965&quot;&gt;    body&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-dark:#BD976A&quot;&gt; JSON&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;({&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; message&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; signature&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;  }).&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;then&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#BD976A&quot;&gt;res&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#D73A49;--shiki-dark:#666666&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt; res&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;  console&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#6F42C1;--shiki-dark:#80A665&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#BD976A&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完整的源碼可以在 Github 的 &lt;a href=&quot;https://github.com/yurenju/nestjs-siwe-example&quot;&gt;@yurenju/nestjs-siwe-example&lt;/a&gt; 找到。&lt;/p&gt;
&lt;h3 id=&quot;結論&quot;&gt;結論&lt;/h3&gt;
&lt;p&gt;以上就是採用 NestJS 實作的最小化 SIWE 整合方式，實際上開發的時候還會需要考慮更多的事情，比如說第一次登入成功後會需要用資料庫建立一筆帳號資訊；或是登入後會需要結合 JWT 在後續的標頭上保持登入狀態，這些建構流程可以參考 &lt;a href=&quot;https://docs.nestjs.com/security/authentication&quot;&gt;NestJS 登入驗證的文件&lt;/a&gt; 以及 &lt;code&gt;passport-ethereum-siwe&lt;/code&gt; 的 &lt;a href=&quot;https://github.com/jaredhanson/passport-ethereum&quot;&gt;README&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;從一個 Ethereum 使用者的觀點來看，任何非操作鏈上資產的服務採用 SIWE 是再合理不過的方式，比如說 &lt;a href=&quot;https://snapshot.org/&quot;&gt;snapshot&lt;/a&gt; 的投票或是 &lt;a href=&quot;https://zapper.xyz/&quot;&gt;zapper&lt;/a&gt; 的資產管理服務，使用 Ethereum 帳號已經是使用區塊鏈應用的日常，既能達到原本集中式帳戶管理所需要的便利性，同時又可以將帳戶的管理權力操控在自己手中，而非可以隨時無端停用使用者帳號的大企業。&lt;/p&gt;
&lt;p&gt;但從一個沒接觸過區塊鏈的使用者來看，這還存在著海溝一般深的隔閡。建立一個 Ethereum 帳號可能會需要 MetaMask 這類型的瀏覽器擴充套件或是獨立的錢包 app 才能達到，更別說還要注意 Network ID 以及各種安全問題，種種的障礙還不如使用像是 1Password 這樣的密碼管理軟體。&lt;/p&gt;
&lt;p&gt;相對來說，如何讓一般的使用者可以兼具安全、去中心化的方式輕鬆的使用 Ethereum 區塊鏈會是一個更重要的課題，當更多使用者都認為擁有 Ethereum 帳號、使用錢包處理資產是家常便飯時，SIWE 能帶來的便利性才能更好的發揮在每一個使用者身上。&lt;/p&gt;
&lt;p&gt;當然，如果一個針對區塊鏈使用者的鏈下服務，採用 SIWE 作為登入機制幾乎是不用思考的選擇了 😎&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>Less is more, 精簡每日待辦事項</title><link>https://yurenju.blog/zh/posts/2022-10-24_less-is-more-%E7%B2%BE%E7%B0%A1%E6%AF%8F%E6%97%A5%E5%BE%85%E8%BE%A6%E4%BA%8B%E9%A0%85/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2022-10-24_less-is-more-%E7%B2%BE%E7%B0%A1%E6%AF%8F%E6%97%A5%E5%BE%85%E8%BE%A6%E4%BA%8B%E9%A0%85/</guid><pubDate>Mon, 24 Oct 2022 02:25:28 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;前言：這是《&lt;a href=&quot;/posts/2022-01-23_%E5%A6%82%E4%BD%95%E6%A2%B3%E7%90%86%E6%88%91%E7%9A%84%E5%BE%85%E8%BE%A6%E4%BA%8B%E9%A0%85%E9%AD%94%E6%94%B9%E5%AD%90%E5%BD%88%E7%AD%86%E8%A8%98%E6%B3%95/&quot;&gt;如何梳理我的待辦事項  —  魔改子彈筆記法&lt;/a&gt;》單獨把精簡每日待辦事項講得更詳細些，在那篇文章可以看到我完整處理筆記的流程，這篇則是會從短期記憶的角度切入如何只安排最重要的工作來降低心理負擔。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一整天工作結束時，你的心理是怎麼樣的狀態呢？覺得 (1) 今天雜事很多，根本沒完成幾件重要的事情；還是覺得 (2) 今天是超有效率的一天，一口氣完成了預計處理的事情，還額外處理了更多工作呢。&lt;/p&gt;
&lt;p&gt;我兩種狀況都是交互著體驗過，但是隨著負責的事情愈來愈多時，發現每天工作時間都是工作項目驅動著我，而不是我驅動著工作項目，每天都有作不完的事情被推著走。當一天工作結束時，經常覺得自己沒有完成什麼事情  —  即使自己確實做了很多重要決策，讓其他人可以順利工作。&lt;/p&gt;
&lt;p&gt;那個每天都超有效率的我，就這樣逐漸離我而去。我真的很想就像《二十世紀少年》裡面的遠藤健兒一樣，奪回那個他已經失去的標誌《👆》，也就是我的工作任務的主導權。&lt;/p&gt;
&lt;p&gt;不過當我閱讀到跟短期記憶相關的資料後，發現這或許只要改變一下每天工作流程跟心裡的想法就可以減緩這個問題。&lt;/p&gt;
&lt;h3 id=&quot;短期記憶&quot;&gt;短期記憶&lt;/h3&gt;
&lt;p&gt;短期記憶是指人的記憶裡相對短期的暫存區，用來儲存暫時會需要記住的事情，比如說要倒垃圾、關電燈，中午出門時買膠帶等。這些短期記憶的容量是有限的，根據《How To Take Smart Note》裡面提到雖然因人而異，但普遍來說每個人最多可以同時記憶約七件事情。&lt;/p&gt;
&lt;p&gt;短期記憶除了沒辦法同時記住多項事情以外，這些儲存於短期記憶裡面的資訊還會時不時的擾亂心智。比如說我認真的跟 Alice 研究一個系統錯誤時，突然想到 Bob 說等等要協助他一件事情，此時跟 Alice 一起專心研究的事情被打擾了，而且到最後我可能還是忘了 Bob 要我幫忙的事情。&lt;/p&gt;
&lt;p&gt;而大家都知道該怎麼作  —  先把待辦事項紀錄在一個地方，比如說便利貼或是待辦事項軟體，晚點再處理。但如果你沒有定期回顧這些待辦事項並且安排下一步的處理，很快的你的待辦事項就跟《王牌天神》裡面的便利貼一樣多。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;《王牌天神》裡面的一幕，祈禱被轉成便利貼形式，然後整個人被淹沒了&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;240&quot; height=&quot;138&quot; src=&quot;/_astro/1.BO2K8Asj_ZIyzWk.gif&quot; &gt;&lt;/p&gt;
&lt;p&gt;當做完一件事情的時候打開待辦事項軟體時，馬上就會被眼花撩亂的工作事項佔據。而一天結束之後，你會發現在數十條待辦事項裡面，你只完成了三條，每天都累的跟狗一樣也沒做完該做事情。&lt;/p&gt;
&lt;p&gt;當然每個人一天都是固定二十四小時，沒辦法攢出更多時間。但是面對過多的工作項目卻有方法可以讓心理負擔變小一點，主要是管理在短期記憶裡面的內容。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;800&quot; height=&quot;319&quot; src=&quot;/_astro/2.TrF4qLo7_Zrg1yH.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;這分成兩個部分，一個是可靠的&lt;strong&gt;工作事項儲存處&lt;/strong&gt;，另外一個是強迫自己&lt;strong&gt;只專注在最重要的事情&lt;/strong&gt;上面。&lt;/p&gt;
&lt;h3 id=&quot;可靠的工作事項儲存處&quot;&gt;可靠的工作事項儲存處&lt;/h3&gt;
&lt;p&gt;還沒完成的工作項目之所以會跳出來干擾你的思緒，是因為這個工作項目佔據了你的短期記憶。&lt;/p&gt;
&lt;p&gt;而要將這樣的工作事項移出你的短期記憶，其中一個方式是找一個可靠的地方紀錄這些工作項目，比如說待辦事項軟體或者是筆記軟體。這個地方要足夠可靠，最重要的是你會&lt;strong&gt;定期回顧與排序優先次序&lt;/strong&gt;。當你讓大腦理解這是一個可靠的儲存處，並且會被定期的好好照顧時，這些工作通常就會在短期記憶裡面淡忘，進而減低感覺有什麼事情沒有做完的心理負擔。&lt;/p&gt;
&lt;p&gt;這完全只是思考方式與工作流程的改變，但是我這麼做之後，確實這些被記錄下來的工作事項無預期的佔據思緒的次數確實少了很多。&lt;/p&gt;
&lt;p&gt;要做到這件事情建議就用你習慣的紀錄方式，不管是待辦事項軟體或是實體筆記、筆記軟體都可以，只要確定你的工作流程會固定的回訪紀錄在這邊的工作項目即可。&lt;/p&gt;
&lt;p&gt;我自己會透過月誌依照優先程度排序本月重要的工作，並且透過每日筆記的「💡 Notes」章節紀錄每日額外新增的工作。每天早上都會重新回顧在這個欄位裡面的紀錄，並且決定這些工作項目的下一步，不管是今天作、放到月誌裡面這個月作，或是放棄這個工作。&lt;/p&gt;
&lt;p&gt;當有了可靠的紀錄處後，下一步是強迫自己只專注在最重要的事情。&lt;/p&gt;
&lt;h3 id=&quot;專注在最重要的事情&quot;&gt;專注在最重要的事情&lt;/h3&gt;
&lt;p&gt;假如今天你完成了七件工作項目，請思考下面兩種狀況，哪種方式會讓你更愉快：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;早上整理出今天要作 15 項工作，然後完成了 7 項，剩下的 8 項移到明天作&lt;/li&gt;
&lt;li&gt;早上整理出今天最重要的 3 項工作，然後完成了，而且還額外做了 4 項工作&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這兩種安排工作的方法不論是哪種都完成了同樣七項工作，但心理負擔的壓力卻差很多，因為一種是&lt;strong&gt;負面&lt;/strong&gt;的認為自己事情沒做完還偷懶拖到隔天才作；另外一種是&lt;strong&gt;正面&lt;/strong&gt;的覺得自己完成了排定的工作，除此之外還能作更多。&lt;/p&gt;
&lt;p&gt;當可以完成的工作項目都一樣的時候，適時的放過自己，選擇一個讓自己心理比較舒服的方式會更好。&lt;/p&gt;
&lt;p&gt;我自己的作法是這樣：起床剛開始坐到電腦前，會透過 Obsidian 樣板建立一個「今日」的檔案，檔名是今天的日期例如 &lt;code&gt;2022-10-24.md&lt;/code&gt;，上面會預先列出每天固定要作的事情，並且在做完之後就把他打勾，大概十五分鐘內可以完成，完成後就利用 Obsidian 的功能把該章節摺疊起來。&lt;/p&gt;
&lt;p&gt;再次強調我自己用 Obsidian，但這樣的作法用什麼軟體或實體筆記本都可以，根據使用的工具調整作法即可。&lt;/p&gt;
&lt;p&gt;在例行公事中，會把行事曆上面的事情整理到今天的待辦事項，並且標注時間，並且會看會產生工作項目的地方如月誌、電子郵件、公司的工作管理系統等，並且把決定哪些是今天最重要的事情，把這些工作加入到今天的待辦事項，這些工作以&lt;strong&gt;不超過三個&lt;/strong&gt;為限，&lt;strong&gt;只有一個&lt;/strong&gt;更佳。&lt;/p&gt;
&lt;p&gt;而且那些沒被我挑進來，但是也很重要的工作事項，告訴自己永遠都有一個可靠的地方可以找到這些工作事項。我自己會把這個月要做的重要事項依照優先程度排序放在月誌裡面，你也可以根據你的工具調整，找到一個可靠的紀錄處。&lt;/p&gt;
&lt;p&gt;這邊提供一個我每天的工作項目的範例，以下是還沒折疊「🗂 Preparing a day」的狀態：&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; 📝 Tasks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;##&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; 🗂 Preparing a day&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 刷牙洗臉&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 整理書桌&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 量體重&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 開始計時時番茄鐘&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 決定昨天的工作事項什麼要移過來今天，以及什麼事情要放棄&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 整理昨天的 Notes 章節轉換成今天的筆記工作項目或是放棄&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 把昨天做的事情寫到月誌&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 看一下 Weekly 有什麼今天要做的&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 看一下月誌有什麼事情可以今天作&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 把行事曆今天要作的事情放到 Scheduled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 看 Github 通知&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 看 Medium 通知&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;##&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; 🧑‍💻 Work&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;##&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; ☕️ Life&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; 🗓 Scheduled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; 💡 Notes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;-&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;當早上的啟動儀式結束後，「🗂 Preparing a day」章節會被折疊，而今天的工作事項就會被準備完成，今天&lt;strong&gt;所有最重要的事情&lt;/strong&gt;都在這個今日筆記上面了：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;487&quot; height=&quot;817&quot; src=&quot;/_astro/3.CEu1A5WZ_jYQze.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;最重要的事情都在上面，只要專心的把上面的事情做完即可。不要擔心剩下也很重要的事情，告訴自己你有一個&lt;strong&gt;可靠的紀錄處&lt;/strong&gt;，當最重要的工作都做完時，很輕鬆的就可以找出那些次重要的事情，並且加回今天的工作項目裡面。&lt;/p&gt;
&lt;p&gt;如果把事情都做完了，我會開心的放鞭炮 🎉，然後打開月誌看看接下來根據優先次序最重要的事情是什麼，一邊享受著自己完成預期工作的滿足感，一邊挑選下一個最重要的工作繼續做下去。&lt;/p&gt;
&lt;p&gt;不過，經常會有臨時來的工作而被打斷，那該怎麼辦呢？&lt;/p&gt;
&lt;h3 id=&quot;管理臨時工作的優先順序&quot;&gt;管理臨時工作的優先順序&lt;/h3&gt;
&lt;p&gt;當 Alice 跟我說：「等等你可以幫我審閱代碼嗎？」此時我會評估這件事情的優先程度，如果需要今天作，我會排到今天的工作裡面。如果不用今天作，我會把它記錄到 「💡 Notes」章節。&lt;/p&gt;
&lt;p&gt;而 「💡 Notes」章節的工作項目會在明天早上的例行公事裡面被重新審視，依照狀況可能會排到明天作，但如果沒有很急就會被放回去月誌裡面並且依照優先順序排序。&lt;/p&gt;
&lt;p&gt;把工作記錄下來的同時，我也跟 Alice 清楚的說現在沒辦法馬上作，但是會把這件事情記錄下來並且跟他說大概會做的時間，當對方明白這件事情你已經做了一定的前處理時，也會減輕他的心理負擔。&lt;/p&gt;
&lt;p&gt;這樣做讓對方可以預期事情大概什麼時候會處理，如果他認為這件事情更重要，他可以跟我討論重要程度或是把這個需求轉交給其他人，這樣工作的進展雙方就會有個概略的共識。&lt;/p&gt;
&lt;h3 id=&quot;重新取回工作的掌握權&quot;&gt;重新取回工作的掌握權&lt;/h3&gt;
&lt;p&gt;最後一件重要的事情是工作事項一定要回顧，並且有可以考慮丟棄的時刻。我自己在每天早上都會回顧昨天沒做完的工作，評估這件事情到底是今天繼續作、只要這個月作，或是直接放棄。&lt;/p&gt;
&lt;p&gt;每個月開始時也會回顧上個月的工作項目完成狀況，並且考慮哪些要繼續，哪些工作要放棄，每年也會作同樣的事情。&lt;/p&gt;
&lt;p&gt;我自己總是有作不完的事情，但更重要的是了解哪些對自己是最重要的，並且適時的決定什麼時候該放棄一些工作項目。當你的工作多到永遠都做不完時，心理負擔就會逐漸的累積，直到有天無法負荷時崩潰。&lt;/p&gt;
&lt;p&gt;但換個想法跟工作流程讓心理負擔小一點，會發現自己每天的工作都可以完成，還可以額外承擔更多工作，即使可以處理的工作數量都一樣。&lt;/p&gt;
&lt;p&gt;與其讓自己的心理走入一個負面的反饋循環，不如正視自己的心靈健康，果斷的決定放棄一些不夠重要的工作項目，專注在當下最重要的事情上面。&lt;/p&gt;
&lt;p&gt;希望這篇文章也讓你有些收穫，不過也記住不需要完全照作，而是考慮如何減低心理負擔，把工作移出短期記憶並且有個可靠的外部儲存處可以定期回顧並照顧。不管你使用什麼工具，都可以琢磨出一個適合現有工具的方式。&lt;/p&gt;
&lt;p&gt;你對工作的主導權也被搶走了嗎？讓我們一起奪回屬於我們的工作主導權！&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>Over-engineering 是軟體開發者職涯中的一部分</title><link>https://yurenju.blog/zh/posts/2022-06-25_overengineering-%E6%98%AF%E8%BB%9F%E9%AB%94%E9%96%8B%E7%99%BC%E8%80%85%E8%81%B7%E6%B6%AF%E4%B8%AD%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2022-06-25_overengineering-%E6%98%AF%E8%BB%9F%E9%AB%94%E9%96%8B%E7%99%BC%E8%80%85%E8%81%B7%E6%B6%AF%E4%B8%AD%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86/</guid><pubDate>Sat, 25 Jun 2022 03:03:14 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;前言：本篇文章是閱讀&lt;/em&gt; &lt;a href=&quot;https://www.mindtheproduct.com/overengineering-can-kill-your-product/&quot;&gt;&lt;em&gt;Overengineering can kill your product — Mind the Product&lt;/em&gt;&lt;/a&gt; &lt;em&gt;後整理的心得再加上自己的經驗所撰寫而成。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;當開發軟體的時候你是傾向盡量設計的簡單好懂，或者是考慮未來會發生的各種狀況設計一個周全有彈性的系統呢？&lt;/p&gt;
&lt;p&gt;想得太簡單可能會太僵硬、需要經常修改或是複製程式碼，考慮的太多又會讓程式碼變得太複雜而成為另一種難以修改的狀況。只有維持在一個適合該專案的複雜度才能讓開發工作事半功倍，但這件事情其實並不容易做到。&lt;/p&gt;
&lt;p&gt;我們今天想討論的是考慮太多的狀況，也就是過度工程 (Over-engineering)。&lt;/p&gt;
&lt;p&gt;Over-engineering 是指實作或設計一些東西來解決一個你&lt;strong&gt;根本就沒有的問題&lt;/strong&gt;，用過於複雜的方式來解決問題，但其實有更簡單、顯而易見的方式可以解決同樣的問題，而增加了不必要的複雜度。比如說為一個不需要 plugin 的軟體制定了多層次可擴展的 plugin 架構。&lt;/p&gt;
&lt;p&gt;但為什麼這件事情會發生呢？&lt;/p&gt;
&lt;h3 id=&quot;幾個原因&quot;&gt;幾個原因&lt;/h3&gt;
&lt;p&gt;會發生 Over-engineering 的情況大多是無意的。第一個最主要的原因是開發軟體時為了「以防萬一」加入了未來可能會用到的架構或是程式碼，但這個以防萬一的情況發生的機率卻很低甚至根本不會發生。&lt;/p&gt;
&lt;p&gt;第二的原因跟工程師的成長有關。當工程師在學習時會吸收一些新知後，就會在自己認為需要的時候嘗試著使用新學習到的知識，但誰天生就是大師呢？在尚未熟悉的狀況總會拿到槌子就開始到處亂釘釘子，想要到處試試自己新學到的酷東西，並且不知道設計到多複雜是恰到好處的狀況下就出現了 over-engineering。&lt;/p&gt;
&lt;p&gt;不過隨著自己被自己寫的複雜程式碼雷到之後，慢慢的會回歸到比較簡單的寫法。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;800&quot; height=&quot;600&quot; src=&quot;/_astro/1.wytSVtPi_Z2eGe1u.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;圖片修改自 &lt;a href=&quot;https://www.mindtheproduct.com/overengineering-can-kill-your-product/&quot;&gt;Overengineering can kill your product&lt;/a&gt; 插圖&lt;/p&gt;
&lt;p&gt;這個過程通常會來來回回的，有時候會後悔自己應該多考慮一些，有時候會覺得自己怎麼會把事情搞得這麼複雜，最後逐漸收斂到一個平衡的狀況。&lt;/p&gt;
&lt;p&gt;睿智的軟體開發者或許可以避免這個狀況，但我是中過自己的招。&lt;/p&gt;
&lt;p&gt;第三個常見原因是需求不清楚。在這樣的狀況下，開發者會預先作一些假設以及額外的功夫來確保自己不會受到這樣不確定性的影響，這也很有可能會造成 Over-engineering。&lt;/p&gt;
&lt;p&gt;最後一個是你可能需要考慮換工作的情況，就是你工作太無聊了。如果你的工作沒有令人興奮的挑戰要處理，此時你可能會透過嘗試新東西來排解無聊而導致無謂的複雜度。說真的這個我也發生過，年輕的時候我曾經簽了一個很長的工作合約，而其實那邊的開發需求不多，所以我確實會把比較新的技術引入到工作中。&lt;/p&gt;
&lt;h3 id=&quot;造成的影響&quot;&gt;造成的影響&lt;/h3&gt;
&lt;p&gt;當開始實作以後都不會派上用場的功能或架構時，代表花費的時間都沒有實質上的回報，就會開始造成時間的浪費。&lt;/p&gt;
&lt;p&gt;等到開發完成後，通常複雜度會同時影響 (a) 修改、新增功能時所需要的心力 (b) 其他同事要入手所需要花費的心力。有完善文件是比較好的情況，當沒有文件時，即使設計得再優良的架構或程式，如果沒有人可以理解與修改，而且這些複雜度又沒有派上用場，原本的問題又更加嚴重。&lt;/p&gt;
&lt;p&gt;這樣的複雜度也會跟隨著這個產品的生命週期一直存在，當這些架構多存在一天，每個參與這個專案開發的工程師就會一直受到影響，開發與維護成本上甚至有加乘效果。&lt;/p&gt;
&lt;p&gt;上面所造成的影響都會造成要花費更多時間與資金成本在專案上面，如果這是一個新創公司的產品，搞不好因為 Over-engineering 就直接導致把錢燒完關門大吉了。&lt;/p&gt;
&lt;p&gt;如果這是個簡單好懂的實作，即使較沒彈性需要經常修改，但是由於容易理解的關係，可以很快的進行修改，整體成本的花費通常都會比較少。&lt;/p&gt;
&lt;h3 id=&quot;過度工程的例子&quot;&gt;過度工程的例子&lt;/h3&gt;
&lt;p&gt;我自己曾經在 side project 作過自然語言的 BDD 測試規格，這樣的好處是即使不是工程師也可以透過自然語言（也就是中文）撰寫規格，而這些規格會被自動的套入測試案例當中變成一個「可以執行的規格」。&lt;/p&gt;
&lt;p&gt;這聽起來很理想  —  但是如果沒有不懂寫程式的人加入，為什麼不用一般的測試案例，直接讓工程師可以更直觀方便的寫測試呢？&lt;/p&gt;
&lt;p&gt;所幸是這是一個實驗專案，是自己對技術的探索，但想像這樣的技術如果在工作上面使用又沒有實際造成正面的影響就不妙了。&lt;/p&gt;
&lt;p&gt;另外經常發生的就是在產品還在探索階段就引入 Microservice 或是可以承載百萬人以上的複雜基礎建設等。以上這些技術在適合的時機都是非常好的選擇，但是如果在錯誤的時機使用就會造成不必要的痛苦。&lt;/p&gt;
&lt;p&gt;比如說在太早期就架設了 Kubernetes，但是在產品探索的階段時一台 heroku, vercel 或是 EC2 都是更優良的解決方案來探索各種可行性，等到真的需要到使用 Kubernetes 這樣的架構時，那已經是個令人羨慕的擔憂了，代表你的產品很多人使用，需要更有彈性擴充的基礎建設。&lt;/p&gt;
&lt;h3 id=&quot;如何避免&quot;&gt;如何避免&lt;/h3&gt;
&lt;p&gt;其實要警覺到過度工程的苗頭並不容易，除了一些很顯而易見的例子外，大多在討論時並不容易察覺這樣的現象。但有些行動對減少 over-engineering 有所幫助。&lt;/p&gt;
&lt;p&gt;第一件事情就是開發時要盡可能的了解使用者，並且透過跟他們接觸，了解到他們所遇到的問題。產品開發最重要的目的就是要解決使用者的問題，如果我們花費了時間卻沒有解決任何使用者的問題，很多時候都是白費力氣。&lt;/p&gt;
&lt;p&gt;跟使用者有更多的互動可以更感同身受的了解到他們的問題產生共鳴，透過使用者的角度來評估你的工作，而不要單純只用工程的角度來評估。&lt;/p&gt;
&lt;p&gt;同時提高透明度也會有幫助。試想這樣的情境：你的工作每天就是從 backlog 裡面挑出一件工作執行，但是你並不理解這個任務的脈絡是什麼，不知道上面為什麼決定要作這個任務。此時在沒有脈絡的狀況下，就只能以工程的角度來看待工作任務。&lt;/p&gt;
&lt;p&gt;如果專案執行透明度更好的狀況下，成員在認領工作任務時，可以知道任務目的，實作之後對專案與使用者會帶來什麼改善。同樣都是認領工作但有了背景脈絡後，成員就可以知道實際要解決的問題是什麼，解決問題時除了從工程的角度審視外，也可以從使用者的角度來思考解決方法是否可以解決使用者的問題。&lt;/p&gt;
&lt;p&gt;提高透明度的方式有幾個，比如說更好的在 issue tracker 紀錄這些脈絡，或者是營造團隊內每個人都樂意回答問題的氛圍，而不要抱持「你作就對了，問那麼多幹嘛」。&lt;/p&gt;
&lt;p&gt;最後成員之間如果能夠更經常交流知識與經驗也可以降低發生的機率，在 Perpetual Protocol 工作時成員們經常採用 Pair Programming 再加上團隊內的知識分享也是個很不錯交流知識的方法。&lt;/p&gt;
&lt;p&gt;當知識經常在成員之間交流時，不同的開發經驗會在成員之間流動，如果有人曾經遭受到複雜性的荼毒，也可以更好的分享這些經驗。&lt;/p&gt;
&lt;h3 id=&quot;但程式愈簡單愈好嗎&quot;&gt;但程式愈簡單愈好嗎？&lt;/h3&gt;
&lt;p&gt;其實不然。over-engineering 最大的問題是製造了「不必要的複雜度」，但有許多專案都有複雜度的必要。所以問題是「不必要」加上「複雜度」。許多產品還是會需要有「必要的複雜度」，就如同前面舉過的 Kubernetes 的例子，當我們的產品已經需要更彈性與擁有豐富工具的基礎建設來擴充時，我們還是會需要更複雜但可以對專案帶來正面影響的工程方式。&lt;/p&gt;
&lt;p&gt;Over-engineering 之所以難判斷，在於每個人所認知的未來情境都不同，當我們在討論一件工作所考慮到的未來情境到底是「足夠」或是「不足」並不是那麼容易取得共識。&lt;/p&gt;
&lt;p&gt;如果大家對可見的未來的想像類似時，要取得共識是比較容易的。但是有些人特別出類拔萃，他們所擁有的知識所預見的未來可能比我們想像的要更遠。如果從事後諸葛來看幾年前的事情會變得很容易判斷到底是「過度工程」還是「神預測」，但是當下要判斷就不是一件那麼容易的事情。&lt;/p&gt;
&lt;p&gt;在這樣難以判斷的時候就只能透過更了解你的使用者、透明的工作環境以及經常性的知識交流降低 over-engineering 的問題。而身為預言家則需要有更好的溝通能力能夠把你所看到的未來順利的傳遞給大家。&lt;/p&gt;
&lt;h3 id=&quot;結論&quot;&gt;結論&lt;/h3&gt;
&lt;p&gt;就如同我上面說的其中一個原因，工程師在學習知識時，總是會有一段學習的歷程，而這段歷程的實戰經驗卻很難傳授給另外一個人。很多軟體開發的方法論看起來很美好，即使旁人跟你提醒世界總是不完美的時候，當下也不見得能夠接受對方的說法，甚至覺得對方不求上進，不想把事情做好。&lt;/p&gt;
&lt;p&gt;唯有自己踩到了自己設下的陷阱，從各種專案的戰場上歷經風霜的歸來之後，才會從自己獲得的寶貴經驗知道實務上要怎麼恰到好處的解任務。&lt;/p&gt;
&lt;p&gt;面對 over-engineering 我們不該是往死裡打的抵抗它，而是要了解它存在的緣由，並且多跟你的團隊成員討論溝通，強調產品的價值何在，透過各種方法來減少 over-engineering。即使沒辦法有一個完美的解法，也要控制影響在一個小範圍內，透過實驗適度的讓每個人都獲得一些經驗（不管是美好或是苦澀的）。&lt;/p&gt;
&lt;p&gt;然後，繼續往前踏上成為一個更好的工程師的旅途。&lt;/p&gt;
&lt;h3 id=&quot;參考資料&quot;&gt;參考資料&lt;/h3&gt;
&lt;p&gt;如同開頭所述，本篇文章是 &lt;a href=&quot;https://www.mindtheproduct.com/overengineering-can-kill-your-product/&quot;&gt;Overengineering can kill your product — Mind the Product&lt;/a&gt; 閱讀後重新整理的心得再加上自己的經驗所撰寫而成。&lt;/p&gt;
&lt;p&gt;另外也閱讀了前同事的文章 &lt;a href=&quot;http://jonathanspeaking.blogspot.com/2010/10/over-engineering.html&quot;&gt;過度設計 (Over Engineering) | 喲哪桑 Speaking 之專案工作日誌&lt;/a&gt;，這篇文章是超過十年前寫的，看來這個問題過了這麼久還是大家關心的事情。&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>吸收知識的手段：撰寫筆記作為你的黃色小鴨</title><link>https://yurenju.blog/zh/posts/2022-06-05_%E5%90%B8%E6%94%B6%E7%9F%A5%E8%AD%98%E7%9A%84%E6%89%8B%E6%AE%B5%E6%92%B0%E5%AF%AB%E7%AD%86%E8%A8%98%E4%BD%9C%E7%82%BA%E4%BD%A0%E7%9A%84%E9%BB%83%E8%89%B2%E5%B0%8F%E9%B4%A8/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2022-06-05_%E5%90%B8%E6%94%B6%E7%9F%A5%E8%AD%98%E7%9A%84%E6%89%8B%E6%AE%B5%E6%92%B0%E5%AF%AB%E7%AD%86%E8%A8%98%E4%BD%9C%E7%82%BA%E4%BD%A0%E7%9A%84%E9%BB%83%E8%89%B2%E5%B0%8F%E9%B4%A8/</guid><pubDate>Sun, 05 Jun 2022 08:43:52 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;800&quot; height=&quot;800&quot; src=&quot;/_astro/1.UrjQior8_1QHQk4.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;軟體工程師經常有一種共同經驗，當工作遇到問題，正詢問另外一個人時，有時講著講著突然就想到要怎麼解決了。這個過程其實是解釋問題時，為了要讓另外一個人理解問題，自己需要重新組織了關於這個問題的相關知識，很多時候在這個階段就會因為重新組織思考過而自行找到答案了。&lt;/p&gt;
&lt;p&gt;為了要自己也可以透過這種方法想清楚一件事情，軟體工程師流傳一個方法是可以在桌上擺一隻黃色小鴨的玩偶，如果遇到不懂的問題就跟他講解，這樣就可以不用有另外一個人聽你說也可以用這種方法來重新思考問題了。&lt;/p&gt;
&lt;p&gt;吸收知識也可以用同樣的方法，有時候我們在閱讀書籍、網路文獻的時候會覺得自己看懂了，但是經常過一段時間又有人提起相關的事情，通常我們不能夠很快把閱讀過的資訊拿來使用、提出建議，可能需要反覆在網路上查詢、跟別人解釋過後，這段知識才會真的被自己吸收，在相關問題發生時可以想起這些知識並且應用。&lt;/p&gt;
&lt;p&gt;寫文章、演講，或只是經常跟另外一個人分享知識都是很好的吸收知識的方式。寫過文章或演講後，對特定的知識的掌握程度會大幅度的提升，但通常都要花費大量時間閱讀與準備。根據準備的主題範圍可能會需要花上幾天甚至更久的時間。另外也不是所有人都想要面對人群或是網路上的互動。&lt;/p&gt;
&lt;p&gt;所以這邊要講的是另外一種很不錯的方式：透過撰寫筆記的方式來重新組織與理解知識。當你針對你有興趣的主題寫筆記時，你自己會需要重新思考過一遍這個知識並且在腦中重新消化咀嚼。當完成筆記的同時，這個筆記將會成為這個過程的副產品，而真正主要的產物則是你在腦中的思考過程，讓你更深度的了解這個知識。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;筆記只是副產品，真正的產物是你腦中思考咀嚼後內化的知識。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這種寫筆記的方式就像是問問題時候的黃色小鴨，你不需面對其他人的評論指教，但是卻有一樣的效果可以協助你理解知識。&lt;/p&gt;
&lt;p&gt;如果你也打算嘗試這樣的方式吸收知識，你可以參考之前寫過的文章《&lt;a href=&quot;/posts/2022-03-30_%E5%A6%82%E4%BD%95%E7%B5%84%E7%B9%94%E8%88%87%E5%90%B8%E6%94%B6%E7%9F%A5%E8%AD%98%E9%AD%94%E6%94%B9-zettelkasten-%E7%AD%86%E8%A8%98%E6%B3%95/&quot;&gt;如何組織與吸收知識  —  魔改 Zettelkasten 筆記法&lt;/a&gt;》，而本文則敘述了部分《&lt;a href=&quot;https://www.kobo.com/tw/zh/ebook/Fv9wzoJWijqUsdeEs8ZduA&quot;&gt;卡片盒筆記&lt;/a&gt;》所提及的撰寫筆記方式。&lt;/p&gt;
&lt;p&gt;首先，我建議只作你自己有興趣或是覺得重要的筆記，不要作你沒興趣的筆記。因為作筆記這件事情需要有內在的原動力持續的進行下去，如果你想硬啃一些你沒興趣或是覺得沒用的知識很難一直持續作筆記下去。&lt;/p&gt;
&lt;p&gt;撰寫筆記的範圍盡量不要過大，否則就跟寫文章一樣了需要花費的時間太長。我自己寫一篇筆記的時候會預估這篇筆記的閱讀時間不要超過五分鐘，如果要超過五分鐘，就考慮是否要拆成數篇筆記，不過如果真的不能拆那也不用勉強，畢竟是作給自己看的筆記。&lt;/p&gt;
&lt;p&gt;下筆時，如果你有其他參考資料比如說書籍或是網路文章，不要只複製貼上或抄寫一遍，而要閱讀完畢後在腦中思考一次，重新用自己的話重新寫過一次。這個步驟很重要，因為我們需要的主產物是重新思考的過程，而如果沒有重新思考過那只是把別人的資訊謄寫一遍，並不會有太多思考過程可以內化成自己的知識。&lt;/p&gt;
&lt;p&gt;接著這篇筆記要假設另外一個已經有概略背景知識的讀者，從頭到尾閱讀就可以明白這篇筆記所要傳達的想法，雖然這個讀者很有可能就是自己，但是這樣的假設可以讓筆記可以獨立的存在，並且未來回來重讀時可以快速的重新回想起這篇筆記所包含的知識，所以寫完之後重新閱讀一下確保這件事情。&lt;/p&gt;
&lt;p&gt;寫完之後，思考這篇筆記自己有沒有什麼疑問、不同意的地方或是任何的想法，並且也紀錄在筆記裡面。我自己紀錄筆記的時候會有一個「問題」的章節放這些東西。&lt;/p&gt;
&lt;p&gt;最後，當你寫超過一篇這樣形式的筆記後，寫完之後仔細思考一下有沒有跟這篇筆記相關的筆記，並且也記錄下來，這個過程對我來說經常會連結到一些我從來沒想過的筆記，這些被探索到的新連結通常會讓我滿驚喜的。&lt;/p&gt;
&lt;p&gt;我使用 Obsidian 撰寫筆記，我在紀錄一篇這樣的筆記時，會使用這個樣板：&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;alias: []&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; Todo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 思考這篇永久筆記你覺得有趣嗎？重要嗎？有趣或重要的點在哪裡？答不出來就不要記&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 寫上文獻筆記的參考&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 補充上下文的脈絡後，用自己的話轉述寫成一篇筆記&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 重新閱讀一次，確認整篇筆記只要了解概略背景知識就可以讀懂&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 思考這篇筆記自己還有什麼衍生的疑問，有什麼不同意的地方？寫下來&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 打開 local graph 並且把深度開成 2，並且把範圍限定成 path:Slipbox&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 思考這篇筆記跟目前卡片盒裡面的有什麼關聯&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 檢視 graph 裡面有沒有出現一些有可能有關聯的筆記&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 把已經擷取完內容的文獻筆記移動到 Archived 目錄&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [ ] 寫完後刪除待辦事項包含標題&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; Content&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; Question&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;-&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; See Also&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;-&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; Reference&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;-&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你也可以參考這樣的格式修改成適合自己的格式，因為待辦事項一節寫完後會刪除，完成筆記的樣子則如下例是「意志力」的筆記：&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light vitesse-dark&quot; style=&quot;background-color:#fff;--shiki-dark-bg:#121212;color:#24292e;--shiki-dark:#dbd7caee; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;markdown&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;alias: [自我耗損, ego depletion]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;人們原本一直以為意志力是種性格的展現，而不是一項資源。但是現今的研究已經讓我們了解到意志力就跟肌力一樣是一種有限的資源，會快速耗盡，而且需要時間回復。透過訓練可以加強到某種程度，但是幅度有限。&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;但是作有趣的事情並不需要消耗意志力。&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; Question&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; 為什麼我經常覺得意志力耗盡？是因為我作的事情不有趣嗎？&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; See Also&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-light-text-decoration:underline;--shiki-dark:#C98A7D;--shiki-dark-text-decoration:inherit&quot;&gt;自律的完成指定任務是取得成功的重要因素&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#666666;--shiki-dark-font-weight:bold&quot;&gt;#&lt;/span&gt;&lt;span style=&quot;color:#005CC5;--shiki-light-font-weight:bold;--shiki-dark:#4D9375;--shiki-dark-font-weight:bold&quot;&gt; Reference&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-light-text-decoration:underline;--shiki-dark:#C98A7D;--shiki-dark-text-decoration:inherit&quot;&gt;卡片盒筆記#^ad5736&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E36209;--shiki-dark:#D4976C&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt; [&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#032F62;--shiki-light-text-decoration:underline;--shiki-dark:#C98A7D;--shiki-dark-text-decoration:inherit&quot;&gt;卡片盒筆記#^5c35f4&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#666666&quot;&gt;]&lt;/span&gt;&lt;span style=&quot;color:#24292E;--shiki-dark:#DBD7CAEE&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See Also 跟 Reference 實際上會連結到 Obsidian 的另外一篇筆記，而 &lt;code&gt;^hash&lt;/code&gt; 則會連結到特定的段落。&lt;/p&gt;
&lt;p&gt;我自己目前透過這樣的方式大約花了一年的時間，紀錄了大概五百篇筆記，大部分都像上面一樣的短筆記，但有些很難切分的也會比較長，而筆記的格式隨著時間有做了一些調整。這樣作以後我覺得自己對於知識的理解更深入了。隨著筆記增多，我也慢慢在撰寫筆記的時候思考到一些新想法記錄下來。&lt;/p&gt;
&lt;p&gt;重點是我覺得很有成就感 😁&lt;/p&gt;
&lt;p&gt;但壞處是這樣作筆記花費的時間真的很長，雖然只是寫閱讀時間五分鐘的筆記，但是你可能要花上好幾倍的時間撰寫，但如果相較於累積的知識再加上這樣的過程產生的成就感，我覺得很值得。&lt;/p&gt;
&lt;p&gt;如果你也第一次嘗試，剛開始應該會跟我一樣遇到不知道該怎麼執行的問題，我建議先試試看然後慢慢調整自己的作法，因為這樣的筆記法其實重點在思考，所以有些事情很模糊，我也不太懂得怎麼講清楚。&lt;/p&gt;
&lt;p&gt;另外雖然《卡片盒筆記》一書裡面把這個過程講的很像很簡單，每個人都做得到，但我自己施行起來感覺是很困難的，也需要有滿長的一段時間跌跌撞撞。&lt;/p&gt;
&lt;p&gt;如果你沒辦法照著這個方法作也沒什麼關係，或許只是這樣的方式不適合你，也可以再找找有沒有其他更適合的吸收知識方式。我遇到了幾個人談論起這樣的筆記法，真的可以執行的人並不多，我自己可能是原本就經常寫文章，所以寫過程繁複的筆記對我來說摩擦力比較小 😎&lt;/p&gt;</content:encoded><category>技術</category></item><item><title>用散步來消化腦中的思考殘渣</title><link>https://yurenju.blog/zh/posts/2022-05-23_%E7%94%A8%E6%95%A3%E6%AD%A5%E4%BE%86%E6%B6%88%E5%8C%96%E8%85%A6%E4%B8%AD%E7%9A%84%E6%80%9D%E8%80%83%E6%AE%98%E6%B8%A3/</link><guid isPermaLink="true">https://yurenju.blog/zh/posts/2022-05-23_%E7%94%A8%E6%95%A3%E6%AD%A5%E4%BE%86%E6%B6%88%E5%8C%96%E8%85%A6%E4%B8%AD%E7%9A%84%E6%80%9D%E8%80%83%E6%AE%98%E6%B8%A3/</guid><pubDate>Mon, 23 May 2022 00:55:16 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot;  width=&quot;800&quot; height=&quot;600&quot; src=&quot;/_astro/1.DlvBYSFi_bMDXs.webp&quot; &gt;&lt;/p&gt;
&lt;p&gt;我的一天總是很忙，處理工作的事情之餘經常還需要閱讀大量新資訊，而這些資訊有時候會很困擾我，比如說搞不清楚新技術運作方式，或是書籍裡面一些讀起來很彆扭，但自己也說不清楚為什麼。&lt;/p&gt;
&lt;p&gt;上網搜尋資料大多可以解決問題，但更多時候是網路上的各種新資訊與會讓你分心的事情隨處發生，像是朋友的動態、一些技術新進展等等，在查到自己需要的資訊同時，腦袋裡面又放了許多懸而未決的資訊片段，而這些思考殘渣經常偶爾就會在專心工作的時候回想起來，但又沒有足夠的時間好好的梳理消化。&lt;/p&gt;
&lt;p&gt;最近找到了一種有趣的方式可以讓自己把事情想的更透徹一點。我早上會出門沿著家裡附近的公園與河堤散步，走過幾次之後發現這是一個很好的時機跟自己獨處，並且不接受太多網路的雜音。&lt;/p&gt;
&lt;p&gt;所以我就利用這段時間重新回顧審視自己一些還沒想清楚的事情，小至朋友聊天時的反應，技術上搞不清楚的設計原因，大到自己的人生目標。每次散步回來雖然大部分的事情都還是不會有答案，但是當在腦袋裡重新想過一輪之後，這些思考殘渣還是有逐漸被消化、梳理，就像是整理了一部分書桌的那種感覺。偶爾還會在散步的途中終於讓自己心中已久的疑惑找到答案，塵埃落定。&lt;/p&gt;
&lt;p&gt;現在我在每天出門散步前，還會特別先看一下自己最近正在整理的筆記或是想要試著思考得更清楚的事情，出門之後雖然不見得可以找到答案，但那種慢慢的整理的感覺還是存在，回到家後打開房門，還是會覺得心裡的房間被收拾的更整齊了一點。&lt;/p&gt;
&lt;p&gt;註：有興趣的朋友可以了解一下 &lt;a href=&quot;https://zh.m.wikipedia.org/zh-tw/%E8%94%A1%E5%8A%A0%E5%B0%BC%E5%85%8B%E6%95%88%E6%87%89&quot;&gt;蔡加尼克效應&lt;/a&gt;。&lt;/p&gt;</content:encoded><category>技術</category></item></channel></rss>