gyotak-purchase writes two kinds of record to Midnight. Every purchase becomes a commitment, persistentCommit(buyerId32, nonce), which fixes the record in time while keeping the buyer off-chain; the commitment is deliberately the plain SHA-256(nonce ‖ buyerId32), which we measured against an externally computed digest and found identical byte for byte, so a buyer needs neither the Midnight SDK nor GYOTAK’s cooperation to open their own record. Separately, a buyer who chooses to speak publicly adds a second record naming two things: the account they will post from, and their own referral identifier. The pairing is what matters. A handle is self-reported and anyone can claim one; a referral identifier is issued by the operator and reaches the customer only inside their personal connection URL, but it appears in the post itself and so can be copied out of a genuine one. Recorded together, a fabricated post must match a binding on both fields, and only a party holding that buyer’s connection URL can produce that pair. Each record also carries a lotId resolving to the catch published by gyotak-catch, so a claim connects to a landing date, a region, and a cold-chain history. We deployed on Mainnet and exercised the whole path on Preprod, including one real customer order that travelled from an AI-chat purchase through payment, picking, commitment issuance, the buyer’s own claim, and both on-chain records. This report is equally explicit about where the construction stops: a connection URL can be shared or leaked, so the claim is evidenced rather than proven.
gyotak-purchase は Midnight に2種類の記録を書く。すべての購入はコミットメント persistentCommit(buyerId32, nonce) になり、買主をチェーン外に置いたまま記録を時刻に固定する。このコミットメントは意図的に素の SHA-256(nonce ‖ buyerId32) としており、外部で計算したダイジェストと実測でバイト単位の一致を確認した。したがって購入者は Midnight SDK も GYOTAK の協力も必要とせず、自分の記録を開ける。これとは別に、公開の場で語ることを選んだ購入者は、2つのものを名指しする記録を加える — 投稿するアカウントと、自分自身の紹介識別子である。要点はその組み合わせにある。handle は自己申告で誰でも名乗れる。紹介識別子は運用者が発行し、個人の接続 URL の中にしか届かないが、投稿そのものに現れるため本物の投稿から複写できる。両方を記録すれば、捏造された投稿は binding の両方の欄と一致しなければならず、その組を作れるのは当該買主の接続 URL を持つ者だけである。各記録は lotId を併せ持ち、gyotak-catch が公開する水揚げに解決するので、名乗りが水揚げ日・海域・冷凍履歴に接続する。Mainnet にデプロイし、AI チャットでの購入から入金・ピック・コミットメント発行・購入者自身の請求・2本のオンチェーン記録まで、実顧客の注文1件を含めて経路全体を Preprod で実行した。本報告は、この構成がどこで止まるかについても同じだけ明確に書く — 接続 URL は共有も漏洩もされうるので、名乗りは証明されるのではなく根拠づけられるにすぎない。
gyotak-purchase เขียนบันทึกสองชนิดลงบน Midnight ทุกการซื้อจะกลายเป็น commitment persistentCommit(buyerId32, nonce) ซึ่งตรึงบันทึกไว้กับเวลาโดยเก็บผู้ซื้อไว้นอกเชน commitment นี้ถูกออกแบบให้เป็น SHA-256(nonce ‖ buyerId32) ธรรมดาโดยเจตนา และเราวัดเทียบกับค่าที่คำนวณจากภายนอกแล้วตรงกันทุกไบต์ ผู้ซื้อจึงไม่ต้องใช้ Midnight SDK และไม่ต้องอาศัยความร่วมมือจาก GYOTAK เพื่อเปิดบันทึกของตนเอง แยกต่างหากจากนั้น ผู้ซื้อที่เลือกจะพูดในที่สาธารณะจะเพิ่มบันทึกที่สองซึ่งระบุสองสิ่ง คือบัญชีที่เขาจะโพสต์ และตัวระบุผู้แนะนำของเขาเอง สิ่งสำคัญคือการจับคู่ handle เป็นการแจ้งเองและใครก็อ้างได้ ส่วนตัวระบุผู้แนะนำนั้นผู้ดำเนินการเป็นผู้ออกและไปถึงลูกค้าเฉพาะภายใน URL การเชื่อมต่อส่วนตัวของเขา แต่มันปรากฏในโพสต์เอง จึงคัดลอกจากโพสต์จริงได้ เมื่อบันทึกทั้งคู่ไว้ด้วยกัน โพสต์ที่แต่งขึ้นต้องตรงกับ binding ทั้งสองช่อง และมีเพียงผู้ที่ถือ URL การเชื่อมต่อของผู้ซื้อรายนั้นเท่านั้นที่สร้างคู่นี้ได้ แต่ละบันทึกยังมี lotId ที่เชื่อมไปยังการจับปลาที่เผยแพร่โดย gyotak-catch การอ้างสิทธิ์จึงเชื่อมโยงกับวันที่จับ พื้นที่ และประวัติห่วงโซ่ความเย็น เราได้ deploy บน Mainnet และทดสอบเส้นทางทั้งหมดบน Preprod รวมถึงคำสั่งซื้อจริงหนึ่งรายการที่เดินทางจากการซื้อผ่านแชต AI ไปสู่การชำระเงิน การหยิบสินค้า การออก commitment การอ้างสิทธิ์ของผู้ซื้อเอง และบันทึกบนเชนทั้งสอง รายงานนี้ระบุอย่างชัดเจนเท่ากันว่าโครงสร้างนี้หยุดอยู่ตรงไหน URL การเชื่อมต่ออาจถูกแบ่งปันหรือรั่วไหลได้ การอ้างสิทธิ์จึงมีหลักฐานรองรับ แต่ไม่ถึงขั้นพิสูจน์
Review systems answer the question “did this person buy it?” by asserting it themselves. The platform holds the order record, marks the review “verified purchase,” and the reader trusts the platform. That arrangement works while the platform is both honest and present. It does nothing for a post written on a social network the seller does not control, and it degrades quickly once generating a plausible first-hand account costs nothing.
That cost is now approximately zero. A language model will produce a detailed, specific, emotionally convincing account of a meal that never happened, and it will do so faster than a real customer can type. The effect is not simply that fabrications appear; it is that the economics invert. Making content becomes cheap while being found becomes expensive, and attention concentrates on whoever is best at capturing it rather than on whoever has something real to report.
That concentration is what an advertiser is currently paying for. A manufacturer’s budget flows to accounts optimised for impressions — which need not have used the product, or made anything at all — while the customer who did buy it, ate it, and told a friend receives nothing but the time they spent. If the same budget reached buyers instead, it would reach people with first-hand knowledge, and the reader who acted on their account would in turn have something to gain. Whether that is better advertising is a business judgment; what makes it possible at all is narrower and technical: the reader, or an agent acting for the reader, must be able to tell a real purchase from a claimed one.
A commitment published on a public ledger answers exactly that narrow question. It does not make an account true, and it does not judge whether the fish was good. It establishes that a record with this shape existed at block N, that its author could not have back-dated it, and that only a party holding the opening value can claim it. That is a small thing to prove, but it is the thing that fabrications cannot borrow. The rest of this report describes how gyotak-purchase produces such records, how anyone can check them, and precisely where the guarantee stops.
レビュー機能は「この人は本当に買ったのか」という問いに、プラットフォーム自身が答えることで応じている。注文記録を握る事業者が「購入済み」と印を付け、読み手はその事業者を信頼する。この仕組みは、事業者が誠実で、かつその場に存在している限りは機能する。しかし販売者の管理下にない SNS での投稿には何の効力も持たず、もっともらしい体験談を作る費用がゼロに近づいた時点で急速に劣化する。
その費用は現にほぼゼロになった。言語モデルは、実際には起きていない食事について、具体的で感情に訴える詳細な記述を生成する。しかも本物の顧客が入力するより速い。ここで起きるのは単に捏造が現れるということではない。経済が反転するということである。作る費用が下がる一方で、見つけてもらう費用が上がる。そして注目は、報告すべき実体を持つ者ではなく、注目を集めるのが最も上手い者に集中する。
広告主が今支払っているのは、その集中に対してである。製造者の予算は、インプレッションに最適化されたアカウントへ流れる — その製品を使ったとは限らず、そもそも何も作っていないこともある — 一方で、実際に買い、食べ、人に話した顧客が受け取るのは、費やした時間だけである。同じ予算が購入者に届くなら、それは一次情報を持つ人に届く。そしてその記述を見て動いた読み手にも、次は得るものがある。それがより良い広告かどうかは事業判断である。ただしそれを成り立たせる条件はもっと狭く、技術的である。読み手、あるいは読み手の代理として動くエージェントが、実際の購入と、そう主張しているだけのものとを区別できなければならない。
公開台帳に刻まれたコミットメントは、まさにこの狭い問いにだけ答える。記述を真実にするわけではないし、その魚が美味かったかを判定するわけでもない。ある形の記録がブロック N の時点で存在したこと、作成者がそれを遡って偽装できなかったこと、そして開封値を持つ者だけがそれを自分のものと主張できることを、確立する。証明する内容としては小さい。だがそれは、捏造が借りてくることのできない唯一のものである。以下では gyotak-purchase がその記録をどう作り、誰がどう確認でき、その保証がどこで止まるかを述べる。
ระบบรีวิวตอบคำถามที่ว่า “คนนี้ซื้อจริงหรือไม่” ด้วยการที่แพลตฟอร์มยืนยันเอง แพลตฟอร์มถือบันทึกคำสั่งซื้อ ติดป้ายว่า “ยืนยันการซื้อแล้ว” และผู้อ่านก็เชื่อแพลตฟอร์มนั้น กลไกนี้ใช้ได้ตราบที่แพลตฟอร์มทั้งซื่อสัตย์และยังคงอยู่ แต่ไม่มีผลใดๆ กับโพสต์บนโซเชียลที่ผู้ขายไม่ได้ควบคุม และเสื่อมลงอย่างรวดเร็วเมื่อการสร้างเรื่องเล่าที่ดูน่าเชื่อไม่มีต้นทุน
ต้นทุนนั้นแทบเป็นศูนย์แล้วในปัจจุบัน โมเดลภาษาสร้างคำบรรยายมื้ออาหารที่ไม่เคยเกิดขึ้นได้อย่างละเอียด เจาะจง และกินใจ เร็วกว่าที่ลูกค้าจริงจะพิมพ์ได้เสียอีก สิ่งที่เกิดขึ้นไม่ใช่เพียงการปรากฏของเรื่องแต่ง แต่คือการกลับด้านของเศรษฐศาสตร์ การผลิตเนื้อหามีต้นทุนถูกลง ขณะที่การถูกค้นพบมีต้นทุนแพงขึ้น และความสนใจก็กระจุกตัวอยู่ที่ผู้ที่เก่งที่สุดในการดึงความสนใจ ไม่ใช่ผู้ที่มีเรื่องจริงจะเล่า
การกระจุกตัวนั้นคือสิ่งที่ผู้ลงโฆษณากำลังจ่ายเงินให้อยู่ในขณะนี้ งบประมาณของผู้ผลิตไหลไปยังบัญชีที่ปรับให้เหมาะกับยอดการมองเห็น — ซึ่งอาจไม่เคยใช้สินค้านั้น หรือไม่เคยสร้างอะไรเลย — ในขณะที่ลูกค้าที่ซื้อจริง กินจริง และเล่าให้เพื่อนฟัง ได้รับเพียงเวลาที่เสียไป หากงบประมาณเดียวกันไปถึงผู้ซื้อแทน มันจะไปถึงคนที่มีข้อมูลโดยตรง และผู้อ่านที่ตัดสินใจตามเรื่องเล่านั้นก็จะมีสิ่งที่ได้รับในคราวต่อไปเช่นกัน การที่สิ่งนี้เป็นโฆษณาที่ดีกว่าหรือไม่เป็นเรื่องของการตัดสินใจทางธุรกิจ แต่สิ่งที่ทำให้มันเป็นไปได้เลยนั้นแคบกว่าและเป็นเรื่องทางเทคนิค ผู้อ่าน หรือเอเจนต์ที่ทำงานแทนผู้อ่าน ต้องแยกแยะการซื้อจริงออกจากการกล่าวอ้างว่าซื้อได้
commitment ที่เผยแพร่บนบัญชีสาธารณะตอบคำถามแคบๆ นี้พอดี มันไม่ได้ทำให้เรื่องเล่าเป็นความจริง และไม่ได้ตัดสินว่าปลานั้นอร่อยหรือไม่ แต่ยืนยันว่าบันทึกรูปแบบนี้มีอยู่ ณ บล็อก N ผู้สร้างไม่สามารถย้อนวันที่ได้ และมีเพียงผู้ที่ถือค่าเปิดเท่านั้นที่อ้างสิทธิ์ได้ สิ่งที่พิสูจน์ได้นั้นเล็กน้อย แต่มันคือสิ่งเดียวที่เรื่องแต่งยืมมาไม่ได้ ส่วนที่เหลือของรายงานนี้อธิบายว่า gyotak-purchase สร้างบันทึกดังกล่าวอย่างไร ใครตรวจสอบได้อย่างไร และการรับประกันหยุดอยู่ตรงไหน
The contract is 151 lines of Compact (pragma language_version >= 0.22) with three ledger fields — purchases, bindings and owner — and six circuits beyond the constructor: the pure helper publicKey, the two writes recordPurchase and recordXBinding, rotateOwner, and the read-only verifyPurchase and verifyXBinding. Its structure follows gyotak-catch v3 (TR-2026-012) with two differences: there is no GPS range proof, because a purchase has no coordinates, and the value being committed is the buyer’s identifier rather than a location.
The buyer’s identifier and the opening nonce enter recordPurchase as witnesses and never appear on chain:
const buyerId = getBuyerId(purchaseId); const nonce = getPurchaseNonce(purchaseId); const purchaseCommitment = persistentCommit<Bytes<32>>(buyerId, nonce);
The design decision that matters is what persistentCommit computes. For a Bytes<32> value it is the plain SHA-256(nonce ‖ buyerId32) — no domain separation, no field arithmetic, nothing a verifier would need a Midnight library to reproduce. We treated this as a claim to be measured rather than assumed. Before recording anything we computed the digest externally in Node, then submitted the transaction and read the stored commitment back from the indexer. The two matched exactly (§ 4).
The consequence is the point of the whole design. A buyer holding their nonce can verify their own record with one hash on any machine, in any language, with no dependency on GYOTAK and no dependency on Midnight tooling. Had we used a Midnight-specific commitment scheme, the guarantee would still hold cryptographically but only for verifiers willing to install our stack — which, for a reader scrolling a social feed, is the same as no guarantee at all.
A commitment alone establishes that someone bought a lot. It says nothing about who is posting. recordXBinding adds that link as a separate record in a separate ledger map:
purchases every purchase; the buyer is hidden inside a commitment bindings the subset a buyer has claimed, naming where they post and who they are
A binding holds two identifiers. The first is the account handle the buyer will post from. The second is their own referral identifier — the value GYOTAK issues to a customer when their payment is confirmed, which the customer uses to earn a commission when someone buys through their link.
An earlier version of this contract recorded only the handle, and this report said plainly that the handle proves nothing about who is posting: it is self-reported, and the operator does not verify that the account belongs to the person claiming it. That remains true. What changed is that the handle is no longer alone.
A referral identifier is different in kind. GYOTAK issues it, and it reaches the customer only inside their personal connection URL — the address they use to talk to our ordering system. Holding it is evidence of having been that customer. But it is not sufficient on its own either, and for the opposite reason: it appears in the post, because publishing it is the whole point. Anyone reading a genuine post can copy the referral URL out of it.
The pairing is what works. A fabricated post must match a binding on both fields — the account it was published from, and the referral URL it carries. Copying a referral identifier out of someone else’s post fails because the handle recorded beside it belongs to that other person. Claiming a handle that is not yours fails because you cannot produce the matching referral identifier. Only a party holding that buyer’s connection URL can produce the pair.
This is evidence, not proof. A connection URL can be shared, or leaked, and the operator would not detect it. § 5 states what remains.
Neither value is hashed. A hashed value would still be verifiable — a reader who sees the post has both in front of them and could compute the digest — but it would require every verifier to run a hash, which for a reader scrolling a social feed is the difference between checking and not checking. Neither value is secret: the buyer publishes both when they post.
The cost is enumerability, and it is larger for the referral identifier than for the handle. A referral identifier is stable across a buyer’s purchases. Anyone who reads it from one post can find every binding that buyer has made — not only this purchase, but all of them, past and future.
We consider that a property rather than a defect. A reader can distinguish someone who bought once and keeps promoting from someone who buys repeatedly, and that distinction is exactly what a reader wants. But it is also a consequence a buyer may not anticipate from publishing a single post, and it cannot be undone. A buyer who stays silent is unaffected: their purchases appear in purchases as commitments and nowhere else, which is the ordinary case and the reason the two maps are separate.
Both writing circuits constrain the caller-supplied timestamp to within ±600 seconds of the producing block’s clock, using blockTimeGte(t - 600) and blockTimeLte(t + 600) — the same window used by gyotak-temp-log and gyotak-catch v3. Neither a purchase nor a binding can be back-dated to precede a post that motivated it.
Writes are insert-only in both maps: a second write to the same key is rejected. There is no update path and no delete path anywhere in the contract. Neither a handle nor a referral identifier can be reassigned after the fact, which is what makes a binding’s block time meaningful as evidence that the claim preceded the post it supports.
lotId is SHA-256("gyotak:lot:" ‖ catch_report_id), stored in plaintext. It resolves to the landing recorded by gyotak-catch v3, which carries species, region, and landing date, and which in turn links to the hourly storage temperatures published by gyotak-temp-log. A purchase commitment is therefore not an isolated token: it names a specific physical lot whose provenance is independently readable on the same chain.
All three state-mutating circuits gate on assert(publicKey(localSecretKey()) == owner). The public key derivation uses the domain tag gyotak:purchase:pk:, distinct from the tags used by the other GYOTAK contracts (gyotak:pk: and gyotak:templog:pk:), so a single operator secret yields a different owner key per contract and authorization does not leak across them. rotateOwner allows the admin key to change without redeploying, preserving the contract address and therefore every verification link already published.
契約は Compact 151 行(pragma language_version >= 0.22)。ledger フィールドは purchases、bindings、owner の3つ、コンストラクタを除く circuit は6つ — pure な補助 publicKey、書き込みの recordPurchase と recordXBinding、rotateOwner、読み取り専用の verifyPurchase と verifyXBinding。構造は gyotak-catch v3(TR-2026-012)を踏襲し、違いは2点。購入に座標は無いので GPS の範囲証明が無いこと、そしてコミットする対象が位置ではなく購入者の識別子であること。
購入者の識別子と開封 nonce は witness として recordPurchase に入り、チェーンには現れない。
const buyerId = getBuyerId(purchaseId); const nonce = getPurchaseNonce(purchaseId); const purchaseCommitment = persistentCommit<Bytes<32>>(buyerId, nonce);
設計上の要点は、persistentCommit が何を計算するかにある。Bytes<32> に対しては素の SHA-256(nonce ‖ buyerId32) である。ドメイン分離も体上の演算も無く、検証者が再現するのに Midnight のライブラリを必要とするものは何も含まれない。これを前提とせず、測るべき主張として扱った。刻印の前に Node で外部からダイジェストを計算し、その後トランザクションを送信して、保存されたコミットメントを indexer から読み戻した。両者は完全に一致した(§ 4)。
この帰結が設計全体の目的である。nonce を持つ購入者は、任意の環境・任意の言語でハッシュを一回計算するだけで自分の記録を検証できる。GYOTAK にも Midnight のツールにも依存しない。仮に Midnight 固有のコミットメント方式を採っていれば、暗号学的な保証は同じでも、それを確かめられるのは我々のスタックを導入する意思のある検証者に限られる。SNS を流し読みする読み手にとって、それは保証が無いのと同じである。
コミットメント単体が確立するのは「誰かがそのロットを買った」ことまでで、投稿しているのが誰かについては何も言わない。recordXBinding は、その結び付きを別の ledger マップの別の記録として加える。
purchases すべての購入。買主はコミットメントの内側に隠れている bindings そのうち買主が名乗ったもの。どこで投稿し誰であるかを示す
binding は2つの識別子を持つ。1つ目は買主が投稿するアカウント名。2つ目は買主自身の紹介識別子 — 入金が確認された時点で GYOTAK が顧客に発行する値で、自分のリンク経由で誰かが購入したときに報酬を受け取るためのものである。
本契約の以前の版は handle だけを記録していた。そして本報告は、handle は投稿者が誰かについて何も証明しないと率直に述べていた。自己申告であり、運用者はそのアカウントが名乗った人のものかを検証していない。それは今も変わらない。変わったのは、handle がもはや単独ではないという点である。
紹介識別子は性質が違う。GYOTAK が発行し、個人の接続 URL — 顧客が我々の注文システムと話すためのアドレス — の中にしか届かない。それを持っていること自体が、その顧客であったことの根拠になる。ただしこれも単独では足りず、理由は handle と逆である。投稿に現れるからだ。公開することがそもそもの目的なのだから。本物の投稿を読んだ者は、そこから紹介 URL を複写できる。
効くのは組み合わせである。捏造された投稿は binding の両方の欄と一致しなければならない — それが公開されたアカウントと、載っている紹介 URL の両方と。他人の投稿から紹介識別子を複写しても、その隣に記録された handle は他人のものなので失敗する。自分のものでない handle を名乗っても、対応する紹介識別子を作れないので失敗する。その組を作れるのは、当該買主の接続 URL を持つ者だけである。
これは根拠であって、証明ではない。接続 URL は共有も漏洩もされうるし、運用者はそれを検知できない。§ 5 で何が残るかを述べる。
どちらの値もハッシュしていない。ハッシュしても検証は可能である — 投稿を見た読み手は両方を手元に持っているのだからダイジェストを計算できる — が、それはすべての検証者にハッシュ計算を強いる。SNS を流し読みする読み手にとって、それは確かめるか確かめないかの分かれ目になる。どちらの値も秘密ではない。買主が投稿するときに自分で公開するものである。
代償は列挙可能性であり、それは handle より紹介識別子の方が大きい。紹介識別子は買主の購入をまたいで不変である。ひとつの投稿からそれを読んだ者は、その買主が作ったすべての binding を見つけられる — この購入だけでなく、過去も未来も含めたすべてを。
我々はこれを欠陥ではなく性質だと考えている。読み手は、一度だけ買って宣伝し続ける者と、繰り返し買っている者とを区別できる。その区別こそ読み手が欲しいものである。だがこれは、一つの投稿を公開したことから買主が予期しないかもしれない帰結でもあり、取り消せない。沈黙を選んだ買主には影響しない。その購入は purchases にコミットメントとして現れるだけで、他のどこにも現れない。それが通常の状態であり、2つのマップを分けている理由である。
書き込み側の2つの circuit は、いずれも呼び出し側が渡した timestamp を、生成ブロックの時刻から ±600 秒以内に blockTimeGte(t - 600) と blockTimeLte(t + 600) で拘束する。gyotak-temp-log および gyotak-catch v3 と同じ窓である。購入も binding も、それを動機づけた投稿に先立つ日時で作ることはできない。
どちらのマップも書き込みは挿入のみで、同一キーへの二度目の書き込みは拒否される。契約のどこにも更新経路も削除経路も存在しない。handle も紹介識別子も後から差し替えられない。これが、binding のブロック時刻を「その名乗りが投稿に先立っていた」証拠として意味あるものにしている。
lotId は SHA-256("gyotak:lot:" ‖ catch_report_id) で、平文で保存される。これは gyotak-catch v3 が記録した水揚げに解決し、そこには魚種・海域・水揚げ日が含まれ、さらに gyotak-temp-log が公開する毎時の保管温度へと繋がる。購入のコミットメントは孤立した記号ではない。同じチェーン上で来歴を独立に読める、特定の物理ロットを名指ししている。
状態を変える3つの circuit は、いずれも assert(publicKey(localSecretKey()) == owner) で門を設けている。公開鍵の導出にはドメインタグ gyotak:purchase:pk: を用い、これは他の GYOTAK 契約が使うタグ(gyotak:pk: と gyotak:templog:pk:)と異なる。したがって運用者の単一の秘密から契約ごとに異なる owner 鍵が導かれ、認可が契約をまたいで漏れることはない。rotateOwner により、再デプロイなしに管理鍵を交換できる。契約アドレスが保たれるため、既に公開した検証リンクはすべて有効なままである。
คอนแทรกต์มี 151 บรรทัดในภาษา Compact (pragma language_version >= 0.22) มี ledger สามฟิลด์คือ purchases bindings และ owner และมีวงจรหกตัวนอกเหนือจาก constructor ได้แก่ publicKey ที่เป็น pure การเขียนสองตัวคือ recordPurchase และ recordXBinding rotateOwner และการอ่านอย่างเดียวคือ verifyPurchase กับ verifyXBinding โครงสร้างตาม gyotak-catch v3 (TR-2026-012) ต่างกันสองจุด คือไม่มีการพิสูจน์ช่วง GPS เพราะการซื้อไม่มีพิกัด และสิ่งที่ commit คือตัวระบุผู้ซื้อแทนที่จะเป็นตำแหน่ง
ตัวระบุผู้ซื้อและ nonce สำหรับเปิดเข้าสู่ recordPurchase ในฐานะ witness และไม่ปรากฏบนเชน
const buyerId = getBuyerId(purchaseId); const nonce = getPurchaseNonce(purchaseId); const purchaseCommitment = persistentCommit<Bytes<32>>(buyerId, nonce);
การตัดสินใจเชิงออกแบบที่สำคัญคือ persistentCommit คำนวณอะไร สำหรับค่า Bytes<32> มันคือ SHA-256(nonce ‖ buyerId32) ธรรมดา ไม่มีการแยกโดเมน ไม่มีเลขคณิตบนฟิลด์ ไม่มีสิ่งใดที่ผู้ตรวจสอบต้องใช้ไลบรารีของ Midnight เพื่อทำซ้ำ เราถือว่านี่เป็นข้อกล่าวอ้างที่ต้องวัด ไม่ใช่สิ่งที่สมมติเอา ก่อนบันทึกใดๆ เราคำนวณ digest จากภายนอกด้วย Node แล้วจึงส่งธุรกรรมและอ่าน commitment ที่เก็บไว้กลับมาจาก indexer ทั้งสองค่าตรงกันพอดี (§ 4)
ผลที่ตามมาคือเป้าหมายของการออกแบบทั้งหมด ผู้ซื้อที่ถือ nonce ของตนตรวจสอบบันทึกของตนเองได้ด้วยแฮชเพียงครั้งเดียว บนเครื่องใดก็ได้ ภาษาใดก็ได้ ไม่ต้องพึ่ง GYOTAK และไม่ต้องพึ่งเครื่องมือของ Midnight หากเราใช้รูปแบบ commitment เฉพาะของ Midnight การรับประกันเชิงวิทยาการรหัสลับก็ยังคงอยู่ แต่จะตรวจสอบได้เฉพาะผู้ที่ยินดีติดตั้งชุดเครื่องมือของเรา ซึ่งสำหรับผู้อ่านที่กำลังเลื่อนดูฟีดโซเชียล ก็ไม่ต่างจากการไม่มีการรับประกันเลย
commitment เพียงอย่างเดียวยืนยันได้ว่ามีคนซื้อล็อตนั้น แต่ไม่ได้บอกว่าใครเป็นผู้โพสต์ recordXBinding เพิ่มการเชื่อมโยงนั้นเป็นบันทึกแยกต่างหากในแมป ledger แยกต่างหาก
purchases ทุกการซื้อ ผู้ซื้อถูกซ่อนอยู่ภายใน commitment bindings เฉพาะส่วนที่ผู้ซื้ออ้างสิทธิ์ ระบุว่าโพสต์ที่ไหนและเป็นใคร
binding เก็บตัวระบุสองอย่าง อย่างแรกคือชื่อบัญชีที่ผู้ซื้อจะโพสต์ อย่างที่สองคือตัวระบุผู้แนะนำของเขาเอง — ค่าที่ GYOTAK ออกให้ลูกค้าเมื่อการชำระเงินได้รับการยืนยัน ซึ่งลูกค้าใช้รับค่าตอบแทนเมื่อมีคนซื้อผ่านลิงก์ของเขา
คอนแทรกต์เวอร์ชันก่อนหน้าบันทึกเฉพาะ handle และรายงานฉบับนี้ได้ระบุไว้ตรงๆ ว่า handle ไม่ได้พิสูจน์อะไรเลยเกี่ยวกับตัวผู้โพสต์ เพราะเป็นการแจ้งเอง และผู้ดำเนินการไม่ได้ตรวจสอบว่าบัญชีนั้นเป็นของผู้อ้างสิทธิ์จริง ข้อนั้นยังคงเป็นจริง สิ่งที่เปลี่ยนไปคือ handle ไม่ได้อยู่ลำพังอีกต่อไป
ตัวระบุผู้แนะนำต่างกันโดยธรรมชาติ GYOTAK เป็นผู้ออก และไปถึงลูกค้าเฉพาะภายใน URL การเชื่อมต่อส่วนตัวของเขา — ที่อยู่ที่เขาใช้คุยกับระบบสั่งซื้อของเรา การถือครองมันจึงเป็นหลักฐานว่าเคยเป็นลูกค้ารายนั้น แต่ลำพังก็ยังไม่พอ ด้วยเหตุผลตรงกันข้าม คือมันปรากฏในโพสต์ เพราะการเผยแพร่มันคือจุดประสงค์ทั้งหมด ใครที่อ่านโพสต์จริงก็คัดลอก URL ผู้แนะนำออกมาได้
สิ่งที่ได้ผลคือการจับคู่ โพสต์ที่แต่งขึ้นต้องตรงกับ binding ทั้งสองช่อง คือบัญชีที่เผยแพร่ และ URL ผู้แนะนำที่แนบมา การคัดลอกตัวระบุผู้แนะนำจากโพสต์ของคนอื่นจะล้มเหลว เพราะ handle ที่บันทึกไว้ข้างกันเป็นของคนนั้น การอ้าง handle ที่ไม่ใช่ของตนก็ล้มเหลว เพราะสร้างตัวระบุผู้แนะนำที่ตรงกันไม่ได้ มีเพียงผู้ที่ถือ URL การเชื่อมต่อของผู้ซื้อรายนั้นเท่านั้นที่สร้างคู่นี้ได้
นี่คือหลักฐาน ไม่ใช่การพิสูจน์ URL การเชื่อมต่ออาจถูกแบ่งปันหรือรั่วไหล และผู้ดำเนินการจะไม่ตรวจพบ § 5 ระบุว่าอะไรยังคงเหลืออยู่
ไม่มีค่าใดถูกแฮช ค่าที่แฮชแล้วก็ยังตรวจสอบได้ — ผู้อ่านที่เห็นโพสต์มีทั้งสองค่าอยู่ตรงหน้าและคำนวณ digest ได้ — แต่จะบังคับให้ผู้ตรวจสอบทุกคนต้องรันแฮช ซึ่งสำหรับผู้อ่านที่เลื่อนดูฟีดโซเชียลคือความต่างระหว่างตรวจกับไม่ตรวจ ทั้งสองค่าไม่ใช่ความลับ ผู้ซื้อเป็นคนเผยแพร่เองเมื่อโพสต์
ต้นทุนคือการถูกไล่เรียงได้ และสำหรับตัวระบุผู้แนะนำนั้นมากกว่า handle เพราะตัวระบุผู้แนะนำคงที่ข้ามการซื้อทุกครั้งของผู้ซื้อ ใครที่อ่านมันจากโพสต์เดียวก็หา binding ทั้งหมดที่ผู้ซื้อรายนั้นเคยทำได้ — ไม่เฉพาะการซื้อครั้งนี้ แต่ทั้งหมด ทั้งอดีตและอนาคต
เราถือว่านี่เป็นคุณสมบัติ ไม่ใช่ข้อบกพร่อง ผู้อ่านแยกแยะได้ระหว่างคนที่ซื้อครั้งเดียวแล้วโปรโมตเรื่อยๆ กับคนที่ซื้อซ้ำ และการแยกแยะนั้นคือสิ่งที่ผู้อ่านต้องการพอดี แต่มันก็เป็นผลที่ผู้ซื้ออาจคาดไม่ถึงจากการเผยแพร่โพสต์เพียงโพสต์เดียว และย้อนกลับไม่ได้ ผู้ซื้อที่เลือกเงียบไม่ได้รับผลกระทบ การซื้อของเขาปรากฏใน purchases ในฐานะ commitment และไม่ปรากฏที่อื่น นั่นคือกรณีปกติ และเป็นเหตุผลที่แยกสองแมปออกจากกัน
วงจรที่เขียนสถานะทั้งสองจำกัด timestamp ที่ผู้เรียกส่งมาให้อยู่ภายใน ±600 วินาทีจากนาฬิกาของบล็อกที่สร้าง โดยใช้ blockTimeGte(t - 600) และ blockTimeLte(t + 600) ซึ่งเป็นช่วงเดียวกับที่ gyotak-temp-log และ gyotak-catch v3 ใช้ ทั้งการซื้อและ binding จึงไม่สามารถย้อนวันที่ให้มาก่อนโพสต์ที่เป็นเหตุจูงใจได้
ทั้งสองแมปเขียนแบบแทรกเท่านั้น การเขียนครั้งที่สองด้วยคีย์เดิมจะถูกปฏิเสธ ไม่มีเส้นทางแก้ไขและไม่มีเส้นทางลบที่ใดในคอนแทรกต์ ทั้ง handle และตัวระบุผู้แนะนำเปลี่ยนภายหลังไม่ได้ นี่คือสิ่งที่ทำให้เวลาบล็อกของ binding มีความหมายในฐานะหลักฐานว่าการอ้างสิทธิ์เกิดก่อนโพสต์ที่มันสนับสนุน
lotId คือ SHA-256("gyotak:lot:" ‖ catch_report_id) เก็บเป็นข้อความธรรมดา ค่านี้เชื่อมไปยังการขึ้นปลาที่บันทึกโดย gyotak-catch v3 ซึ่งมีชนิดปลา พื้นที่ และวันที่จับ และเชื่อมต่อไปยังอุณหภูมิจัดเก็บรายชั่วโมงที่เผยแพร่โดย gyotak-temp-log commitment การซื้อจึงไม่ใช่สัญลักษณ์ที่ลอยอยู่โดดๆ แต่ระบุล็อตทางกายภาพเฉพาะที่ตรวจสอบที่มาได้อย่างอิสระบนเชนเดียวกัน
วงจรที่เปลี่ยนสถานะทั้งสามตัวมีด่าน assert(publicKey(localSecretKey()) == owner) การอนุมานกุญแจสาธารณะใช้ domain tag gyotak:purchase:pk: ซึ่งต่างจากแท็กของคอนแทรกต์ GYOTAK ตัวอื่น (gyotak:pk: และ gyotak:templog:pk:) ความลับเดียวของผู้ดำเนินการจึงให้กุญแจเจ้าของที่ต่างกันในแต่ละคอนแทรกต์ และสิทธิ์ไม่รั่วข้ามกัน rotateOwner ให้เปลี่ยนกุญแจผู้ดูแลได้โดยไม่ต้อง deploy ใหม่ ที่อยู่คอนแทรกต์จึงคงเดิมและลิงก์ตรวจสอบที่เผยแพร่ไปแล้วยังใช้ได้ทั้งหมด
The two records are written at different moments, by different triggers, and the gap between them is where the buyer’s choice lives.
1. Purchase the customer orders, through an AI chat over their own
connection URL
2. Payment confirmed. The referral identifier is issued at this moment,
automatically, and reaches the customer in their confirmation
3. Picking staff pick the physical packs; the shipped lot resolves
to exactly one catch record, or no commitment is issued
4. Commitment the operator issues a purchase commitment (nonce + buyerId32)
5. On-chain a mirror process records it: recordPurchase
── the buyer may stop here; nothing public names them ──
6. Claim the buyer asks for their proof and gives a handle
7. On-chain the mirror records the link: recordXBinding
Steps 1 to 5 happen for every paid order whose lot resolves cleanly. Steps 6 and 7 happen only when the buyer asks.
Step 6 is where the two identifiers come together, and how it is done matters. The claim arrives over the buyer’s own connection URL. The tool that receives it takes no customer identifier as an argument — the connection itself is the only accepted credential — and the operator looks up that customer’s referral identifier server-side rather than accepting one from the caller. A referral connection cannot be used to claim: those connections are deliberately excluded from identity resolution, so a referrer cannot claim someone else’s purchase.
Step 3 deserves a note. A commitment is issued only when the picked lot resolves to exactly one catch record on a chain the operator has already published. If two landings share a date and species, the resolution is ambiguous and no commitment is issued at all. We would rather publish nothing than publish a lot identifier that points at the wrong landing.
2つの記録は別の時点で、別の契機によって書かれる。その間隔こそが、買主の選択の置かれる場所である。
1. 購入 顧客が自分の接続 URL を通した AI チャットから注文する
2. 入金 確認される。この時点で紹介識別子が自動的に発行され、
確認の連絡とともに顧客に届く
3. ピック 現場が実物のパックを取り出す。出荷ロットが水揚げ記録
ちょうど1件に解決すること。しなければ発行されない
4. 発行 運用者が購入コミットメントを発行する(nonce + buyerId32)
5. 刻印 mirror が記録する: recordPurchase
── 買主はここで止まってよい。誰も名指ししない ──
6. 請求 買主が自分の証明を求め、handle を渡す
7. 刻印 mirror が結び付きを記録する: recordXBinding
1 から 5 は、入金済みでロットがきれいに解決したすべての注文で起きる。6 と 7 は、買主が求めたときにだけ起きる。
2つの識別子が揃うのは 6 であり、その揃え方が肝要である。請求は買主自身の接続 URL を通って届く。受け取るツールは顧客識別子を引数として取らない — 接続そのものが唯一の資格情報である — そして運用者は、呼び出し側から受け取るのではなく、その顧客の紹介識別子をサーバー側で引く。紹介者用の接続では請求できない。それらの接続は本人性の解決から意図的に除外されており、紹介者が他人の購入を請求することはできない。
3 については一言必要である。コミットメントが発行されるのは、ピックしたロットが、運用者が既に公開済みのチェーン上の水揚げ記録ちょうど1件に解決したときだけである。日付と魚種が同じ水揚げが2件あれば解決は曖昧となり、コミットメントは一切発行されない。誤った水揚げを指すロット識別子を公開するくらいなら、何も公開しない方を選ぶ。
บันทึกทั้งสองถูกเขียนคนละเวลา ด้วยตัวกระตุ้นคนละอย่าง และช่องว่างระหว่างทั้งสองคือที่ที่ทางเลือกของผู้ซื้ออยู่
1. การซื้อ ลูกค้าสั่งซื้อผ่านแชต AI บน URL การเชื่อมต่อของตนเอง
2. การชำระเงิน ยืนยันแล้ว ตัวระบุผู้แนะนำถูกออกโดยอัตโนมัติ ณ ตอนนี้
และไปถึงลูกค้าพร้อมกับการยืนยัน
3. การหยิบ พนักงานหยิบแพ็กจริง ล็อตที่ส่งต้องระบุเป็นบันทึก
การจับปลาเพียงรายการเดียว มิฉะนั้นจะไม่ออก commitment
4. การออก ผู้ดำเนินการออก commitment การซื้อ (nonce + buyerId32)
5. บนเชน กระบวนการ mirror บันทึก: recordPurchase
── ผู้ซื้ออาจหยุดตรงนี้ ไม่มีสิ่งใดสาธารณะระบุตัวเขา ──
6. อ้างสิทธิ์ ผู้ซื้อขอหลักฐานของตนและให้ handle
7. บนเชน mirror บันทึกการเชื่อมโยง: recordXBinding
ขั้นที่ 1 ถึง 5 เกิดกับทุกคำสั่งซื้อที่ชำระเงินแล้วและล็อตระบุได้ชัดเจน ขั้นที่ 6 และ 7 เกิดเฉพาะเมื่อผู้ซื้อร้องขอ
ขั้นที่ 6 คือจุดที่ตัวระบุทั้งสองมาบรรจบกัน และวิธีการนั้นสำคัญ การอ้างสิทธิ์มาถึงผ่าน URL การเชื่อมต่อของผู้ซื้อเอง เครื่องมือที่รับไม่ได้รับตัวระบุลูกค้าเป็นอาร์กิวเมนต์ — ตัวการเชื่อมต่อเองคือข้อมูลรับรองเดียวที่ยอมรับ — และผู้ดำเนินการค้นหาตัวระบุผู้แนะนำของลูกค้ารายนั้นจากฝั่งเซิร์ฟเวอร์ แทนที่จะรับจากผู้เรียก การเชื่อมต่อแบบผู้แนะนำใช้อ้างสิทธิ์ไม่ได้ เพราะถูกกันออกจากการระบุตัวตนโดยเจตนา ผู้แนะนำจึงอ้างสิทธิ์การซื้อของคนอื่นไม่ได้
ขั้นที่ 3 ควรมีหมายเหตุ commitment จะออกก็ต่อเมื่อล็อตที่หยิบระบุเป็นบันทึกการจับปลาเพียงรายการเดียวบนเชนที่ผู้ดำเนินการเผยแพร่ไว้แล้ว หากมีการขึ้นปลาสองครั้งที่วันที่และชนิดปลาตรงกัน การระบุจะกำกวมและจะไม่ออก commitment เลย เราเลือกที่จะไม่เผยแพร่อะไรเลย ดีกว่าเผยแพร่ตัวระบุล็อตที่ชี้ไปยังการขึ้นปลาที่ผิด
The contract is deployed on Midnight Mainnet at d11d52bd5875ecc2e89e97149e0237db20a91c989f30268950e892655a2a2a57 and on Preprod at e413ff91958079d1ee2e4c792fe19a9c14a5d4ed7eb7966d3526308a3ea2f846, both with owner = 20fc1d0d5c405e95c669158a3db32217e2be65247dbea06e243745832af2e1be. The Mainnet ledger is empty at the time of writing; the records below are the Preprod rehearsal that preceded it.
The first pair of records was built by hand to test two claims.
That the commitment is a plain SHA-256. Test values were generated in memory, the digest was computed locally before submission, and the transaction was then sent. Reading the stored record back from the indexer gave:
record PB-20260918-b7f3c55d local SHA-256(nonce ‖ buyerId32) = bdeb7b88c067d51437ccf20222b9032faff29920dfdbff1139be2ec3bbc4a4a3 chain purchaseCommitment = bdeb7b88c067d51437ccf20222b9032faff29920dfdbff1139be2ec3bbc4a4a3 → MATCH
That the referral identifier survives the round trip. A referral identifier is aff_ followed by 32 hex characters — 36 characters, too long for Bytes<32> as ASCII. The hex decodes to 16 raw bytes, which we store right-padded with zeros; aff_ is a constant, so the value is recoverable. A binding was recorded with a handle and a test referral identifier, and both read back matching.
The second pair came from a real customer order, and travelled the entire path in § 3 without hand intervention at any step that a customer would not take themselves. The order was placed through an AI chat over the customer’s own connection URL; payment was confirmed, issuing the referral identifier; staff picked three packs of Kuro-Kanpachi from a lot landed on 2026-09-05; the picked lot resolved to exactly one catch record; a commitment was issued; a mirror process recorded it on chain; the buyer then claimed it through their own connection and supplied a handle; and the mirror recorded the binding with both identifiers.
The mirror re-verifies SHA-256(nonce ‖ buyerId32) against the stored commitment before submitting, and submits only on a match. It also refuses to write a binding for a purchase that is not confirmed on this contract.
The referral identifier read back from this binding reconstructed exactly to the value the operator had issued to that customer when their payment was confirmed.
| Record | Identifier (SDK txId) | On-chain hash | Block | Time (UTC) |
|---|---|---|---|---|
| Preprod deploy | 001ff976…11f8d01 | 6ca2cdac…ce900c | 2,600,073 | 06:31:36 |
| Test purchase | 00e63d61…99862c2 | 917a73d9…b6e576 | 2,600,143 | 06:38:36 |
| Test binding | 00ba7342…b585516 | 908c1748…e7bfdd | 2,600,157 | 06:40:00 |
| Real purchase | 001fa340…2d32636 | 0399eea2…5de27a | 2,604,101 | 13:14:24 |
| Real binding | 00a63f64…86893e5 | 79623f6b…317608 | 2,604,115 | 13:15:48 |
| Mainnet deploy | 00d4ff9f…1aa8db9 | 6da22391…4cfcce | 2,636,444 | 13:22:54 |
In both pairs the binding settled 84 seconds after its purchase — 14 blocks — and the contract’s own committedAt and boundAt preserve the same order. The claim is visibly later than the purchase it refers to.
transactions(offset: { identifier: ... }). The 64-character cryptographic hash is a separate field. Querying the hash field with an identifier value returns invalid transaction hash; we verified this explicitly. Readers using a block explorer should use the hash column above.
The contract source is 6,574 bytes, SHA-256 b4dc7879e5e872d9044747184f8c8bf62bb0a74b3e647b656d0810bfc901235e, compiled with compactc 0.30.0 (language 0.22.0, runtime 0.15.0). A full rebuild from a clean directory, including proving-key generation, reproduced all twenty artefacts under contracts/managed/keys and contracts/managed/zkir byte for byte. The verifier keys stored in both deployed contracts’ state are byte-for-byte identical to the rebuilt files. The per-file SHA-256 table is published in the deployment request accompanying this report.
On Preprod each transaction settled for approximately 0.30 DUST, read from the DustSpendProcessed events on chain rather than from the indexer’s fee field, which reports 1 speck and does not reflect what was actually spent. A purchase and its binding therefore cost about 0.60 DUST. The Mainnet deploy cost 50 DUST, a deliberate fee overhead set for deploys only: a deploy that fails and has to be repeated produces a different contract address, which would invalidate every verification link already published. Recording transactions use the ordinary overhead. The contract holds no assets.
契約は Midnight Mainnet の d11d52bd5875ecc2e89e97149e0237db20a91c989f30268950e892655a2a2a57 と、Preprod の e413ff91958079d1ee2e4c792fe19a9c14a5d4ed7eb7966d3526308a3ea2f846 にデプロイされ、どちらも owner = 20fc1d0d5c405e95c669158a3db32217e2be65247dbea06e243745832af2e1be である。本稿執筆時点で Mainnet の ledger は空であり、以下の記録はそれに先立つ Preprod のリハーサルである。
最初の一組は、2つの主張を検証するために手動で作成した。
コミットメントが素の SHA-256 であること。テスト値をメモリ上で生成し、送信前にローカルでダイジェストを計算してから、トランザクションを送った。indexer から記録を読み戻した結果は次のとおり。
レコード PB-20260918-b7f3c55d ローカル SHA-256(nonce ‖ buyerId32) = bdeb7b88c067d51437ccf20222b9032faff29920dfdbff1139be2ec3bbc4a4a3 チェーン purchaseCommitment = bdeb7b88c067d51437ccf20222b9032faff29920dfdbff1139be2ec3bbc4a4a3 → 一致
紹介識別子が往復に耐えること。紹介識別子は aff_ に続く hex 32 文字 — 36 文字であり、ASCII のままでは Bytes<32> に入らない。hex は 16 バイトに復号され、これを右詰めゼロパディングで保存する。aff_ は定数なので値は復元できる。handle とテスト用の紹介識別子で binding を刻み、両方の読み戻しが一致した。
2組目は実顧客の注文から生まれ、§ 3 の経路全体を、顧客自身が行わない手作業を一切挟まずに通った。注文は顧客自身の接続 URL を通した AI チャットから行われ、入金が確認されて紹介識別子が発行され、現場が 2026-09-05 に水揚げされたロットから Kuro-Kanpachi を3パック取り出し、そのロットは水揚げ記録ちょうど1件に解決し、コミットメントが発行され、mirror がチェーンに刻み、その後買主が自分の接続から請求して handle を渡し、mirror が両方の識別子とともに binding を刻んだ。
mirror は送信前に SHA-256(nonce ‖ buyerId32) を保存されたコミットメントと再照合し、一致した場合にのみ送信する。またこの契約上で confirmed になっていない購入に対しては binding を書かない。
この binding から読み戻した紹介識別子は、入金確認時に運用者がその顧客に発行した値と正確に一致する形に復元された。
| 記録 | Identifier (SDK txId) | オンチェーン hash | Block | 時刻 (UTC) |
|---|---|---|---|---|
| Preprod デプロイ | 001ff976…11f8d01 | 6ca2cdac…ce900c | 2,600,073 | 06:31:36 |
| テスト購入 | 00e63d61…99862c2 | 917a73d9…b6e576 | 2,600,143 | 06:38:36 |
| テスト binding | 00ba7342…b585516 | 908c1748…e7bfdd | 2,600,157 | 06:40:00 |
| 実データ購入 | 001fa340…2d32636 | 0399eea2…5de27a | 2,604,101 | 13:14:24 |
| 実データ binding | 00a63f64…86893e5 | 79623f6b…317608 | 2,604,115 | 13:15:48 |
| Mainnet デプロイ | 00d4ff9f…1aa8db9 | 6da22391…4cfcce | 2,636,444 | 13:22:54 |
どちらの組でも binding は購入の84秒後 — 14ブロック後 — に確定し、契約自身が持つ committedAt と boundAt も同じ順序を保っている。名乗りは、それが指す購入より後であることが目に見える。
transactions(offset: { identifier: ... }) として受け付けるのもこの値である。64 文字の暗号学的 hash は別のフィールドである。hash のフィールドに identifier を渡すと invalid transaction hash が返ることを実測で確認した。エクスプローラを使う読者は上表の hash 列を使うこと。
契約ソースは 6,574 バイト、SHA-256 b4dc7879e5e872d9044747184f8c8bf62bb0a74b3e647b656d0810bfc901235e、compactc 0.30.0(language 0.22.0、runtime 0.15.0)でコンパイルした。証明鍵の生成を含む完全な再ビルドをクリーンなディレクトリで行い、contracts/managed/keys および contracts/managed/zkir の 20 ファイルすべてがバイト単位で再現されることを確認した。デプロイ済みの両契約の state に格納された verifier 鍵も、再ビルドしたファイルとバイト単位で一致する。ファイルごとの SHA-256 表は、本報告に付随する deployment request に掲載している。
Preprod では各トランザクションが約 0.30 DUST で確定した。この値は indexer の fee 欄ではなく、チェーン上の DustSpendProcessed イベントから読んだものである。indexer の fee 欄は 1 speck を返すだけで、実際に支払われた額を表さない。購入と binding の2記録で約 0.60 DUST である。Mainnet のデプロイは 50 DUST を要した。これはデプロイにのみ設定した意図的な上乗せである。デプロイが失敗して再実行すると契約アドレスが変わり、既に公開した検証リンクがすべて無効になるためである。記録のトランザクションは通常の上乗せを用いる。契約は資産を保持しない。
คอนแทรกต์ deploy บน Midnight Mainnet ที่ d11d52bd5875ecc2e89e97149e0237db20a91c989f30268950e892655a2a2a57 และบน Preprod ที่ e413ff91958079d1ee2e4c792fe19a9c14a5d4ed7eb7966d3526308a3ea2f846 ทั้งคู่มี owner = 20fc1d0d5c405e95c669158a3db32217e2be65247dbea06e243745832af2e1be ณ เวลาที่เขียนรายงานนี้ ledger บน Mainnet ยังว่างเปล่า บันทึกด้านล่างคือการซ้อมบน Preprod ที่เกิดขึ้นก่อนหน้า
บันทึกคู่แรกสร้างด้วยมือเพื่อทดสอบข้อกล่าวอ้างสองข้อ
ว่า commitment เป็น SHA-256 ธรรมดา ค่าทดสอบถูกสร้างในหน่วยความจำ คำนวณ digest ในเครื่องก่อนส่งธุรกรรม แล้วจึงส่ง เมื่ออ่านบันทึกกลับจาก indexer ได้ผลดังนี้
บันทึก PB-20260918-b7f3c55d ในเครื่อง SHA-256(nonce ‖ buyerId32) = bdeb7b88c067d51437ccf20222b9032faff29920dfdbff1139be2ec3bbc4a4a3 บนเชน purchaseCommitment = bdeb7b88c067d51437ccf20222b9032faff29920dfdbff1139be2ec3bbc4a4a3 → ตรงกัน
ว่าตัวระบุผู้แนะนำรอดจากการเดินทางไปกลับ ตัวระบุผู้แนะนำคือ aff_ ตามด้วย hex 32 อักขระ — รวม 36 อักขระ ยาวเกินกว่าจะใส่ใน Bytes<32> แบบ ASCII ได้ hex ถอดเป็น 16 ไบต์ ซึ่งเราเก็บโดยเติมศูนย์ด้านขวา aff_ เป็นค่าคงที่ ค่าจึงกู้คืนได้ เราบันทึก binding ด้วย handle และตัวระบุผู้แนะนำทดสอบ และอ่านกลับมาตรงกันทั้งคู่
คู่ที่สองมาจากคำสั่งซื้อจริงของลูกค้า และเดินทางผ่านเส้นทางทั้งหมดใน § 3 โดยไม่มีการแทรกแซงด้วยมือในขั้นตอนใดที่ลูกค้าจะไม่ทำเอง คำสั่งซื้อทำผ่านแชต AI บน URL การเชื่อมต่อของลูกค้าเอง ชำระเงินได้รับการยืนยันและออกตัวระบุผู้แนะนำ พนักงานหยิบ Kuro-Kanpachi สามแพ็กจากล็อตที่ขึ้นปลาเมื่อ 2026-09-05 ล็อตที่หยิบระบุเป็นบันทึกการจับปลาเพียงรายการเดียว มีการออก commitment กระบวนการ mirror บันทึกลงเชน จากนั้นผู้ซื้ออ้างสิทธิ์ผ่านการเชื่อมต่อของตนเองและให้ handle และ mirror บันทึก binding พร้อมตัวระบุทั้งสอง
mirror ตรวจซ้ำ SHA-256(nonce ‖ buyerId32) เทียบกับ commitment ที่เก็บไว้ก่อนส่ง และส่งเฉพาะเมื่อตรงกัน อีกทั้งปฏิเสธที่จะเขียน binding ให้กับการซื้อที่ยังไม่ confirmed บนคอนแทรกต์นี้
ตัวระบุผู้แนะนำที่อ่านกลับจาก binding นี้กู้คืนได้ตรงกับค่าที่ผู้ดำเนินการออกให้ลูกค้ารายนั้นเมื่อการชำระเงินได้รับการยืนยัน
| บันทึก | Identifier (SDK txId) | แฮชบนเชน | บล็อก | เวลา (UTC) |
|---|---|---|---|---|
| Deploy Preprod | 001ff976…11f8d01 | 6ca2cdac…ce900c | 2,600,073 | 06:31:36 |
| การซื้อทดสอบ | 00e63d61…99862c2 | 917a73d9…b6e576 | 2,600,143 | 06:38:36 |
| binding ทดสอบ | 00ba7342…b585516 | 908c1748…e7bfdd | 2,600,157 | 06:40:00 |
| การซื้อจริง | 001fa340…2d32636 | 0399eea2…5de27a | 2,604,101 | 13:14:24 |
| binding จริง | 00a63f64…86893e5 | 79623f6b…317608 | 2,604,115 | 13:15:48 |
| Deploy Mainnet | 00d4ff9f…1aa8db9 | 6da22391…4cfcce | 2,636,444 | 13:22:54 |
ในทั้งสองคู่ binding เกิดขึ้น 84 วินาทีหลังการซื้อ — 14 บล็อก — และ committedAt กับ boundAt ของคอนแทรกต์เองก็รักษาลำดับเดียวกัน การอ้างสิทธิ์เกิดหลังการซื้อที่มันอ้างถึงอย่างเห็นได้ชัด
transactions(offset: { identifier: ... }) ส่วนแฮชเชิงวิทยาการรหัสลับความยาว 64 อักขระเป็นคนละฟิลด์ การส่งค่า identifier ไปยังฟิลด์แฮชจะได้ invalid transaction hash ซึ่งเราตรวจสอบแล้ว ผู้อ่านที่ใช้ block explorer ควรใช้คอลัมน์แฮชด้านบน
ซอร์สคอนแทรกต์มีขนาด 6,574 ไบต์ SHA-256 b4dc7879e5e872d9044747184f8c8bf62bb0a74b3e647b656d0810bfc901235e คอมไพล์ด้วย compactc 0.30.0 (language 0.22.0, runtime 0.15.0) การ build ใหม่ทั้งหมดจากไดเรกทอรีสะอาดรวมถึงการสร้าง proving key ให้ผลตรงกันทุกไบต์ทั้งยี่สิบไฟล์ภายใต้ contracts/managed/keys และ contracts/managed/zkir กุญแจ verifier ที่เก็บในสถานะของคอนแทรกต์ทั้งสองที่ deploy แล้วก็ตรงกับไฟล์ที่ build ใหม่ทุกไบต์ ตาราง SHA-256 รายไฟล์เผยแพร่ในคำขอ deployment ที่แนบกับรายงานนี้
บน Preprod แต่ละธุรกรรมใช้ประมาณ 0.30 DUST ค่านี้อ่านจากเหตุการณ์ DustSpendProcessed บนเชน ไม่ใช่จากฟิลด์ fee ของ indexer ซึ่งรายงานเพียง 1 speck และไม่สะท้อนค่าที่จ่ายจริง การซื้อและ binding ของมันจึงมีต้นทุนประมาณ 0.60 DUST ส่วน deploy บน Mainnet ใช้ 50 DUST ซึ่งเป็นค่าใช้จ่ายเพิ่มที่ตั้งไว้สำหรับการ deploy เท่านั้นโดยเจตนา หาก deploy ล้มเหลวและต้องทำซ้ำจะได้ที่อยู่คอนแทรกต์ใหม่ ซึ่งจะทำให้ลิงก์ตรวจสอบที่เผยแพร่ไปแล้วใช้ไม่ได้ ธุรกรรมการบันทึกใช้ค่าใช้จ่ายเพิ่มตามปกติ คอนแทรกต์ไม่ถือครองสินทรัพย์ใดๆ
Verified:
persistentCommit(buyerId32, nonce) equals an externally computed SHA-256(nonce ‖ buyerId32), byte for byte.Deployed on Mainnet:
d11d52bd5875ecc2e89e97149e0237db20a91c989f30268950e892655a2a2a57, block 2,636,444 (2026-09-18T13:22:54Z). Authorization has been requested; the ledger is empty at the time of writing. An earlier version of this contract was authorized separately and remains deployed with one purchase and one binding recorded; nothing further will be written to it.Disclosed openly — what this does not prove:
検証済み:
persistentCommit(buyerId32, nonce) が、外部で計算した SHA-256(nonce ‖ buyerId32) とバイト単位で一致する。Mainnet デプロイ済み:
d11d52bd5875ecc2e89e97149e0237db20a91c989f30268950e892655a2a2a57、block 2,636,444(2026-09-18T13:22:54Z)。認可を申請済みで、本稿執筆時点で ledger は空である。本契約の以前の版は別途認可を受けており、購入1件と binding 1件を記録したままデプロイされている。そちらには今後何も書き込まない。公開して述べる — これが証明しないこと:
ตรวจสอบแล้ว:
persistentCommit(buyerId32, nonce) ของวงจรเท่ากับ SHA-256(nonce ‖ buyerId32) ที่คำนวณจากภายนอกทุกไบต์Deploy บน Mainnet แล้ว:
d11d52bd5875ecc2e89e97149e0237db20a91c989f30268950e892655a2a2a57 บล็อก 2,636,444 (2026-09-18T13:22:54Z) ได้ยื่นขออนุญาตแล้ว ณ เวลาที่เขียนรายงานนี้ ledger ยังว่างเปล่า คอนแทรกต์เวอร์ชันก่อนหน้าได้รับอนุญาตแยกต่างหากและยังคง deploy อยู่ โดยมีการซื้อ 1 รายการและ binding 1 รายการบันทึกไว้ จะไม่มีการเขียนอะไรเพิ่มลงไปอีกเปิดเผยตามตรง — สิ่งที่ระบบนี้ไม่ได้พิสูจน์:
The Midnight indexer exposes contract state over GraphQL. Query contractAction(address) for the contract address above and read the state field. Records in purchases are keyed by purchaseId and carry purchaseCommitment, lotId, committedAt and schema; records in bindings use the same key and carry handle, referrerId, boundAt and schema. No GYOTAK service is involved in this path; the only party trusted is the indexer operator.
To check a post: take the purchase id from the post, read both records, and compare both identifiers against what the post shows — the account that published it, and the referral URL it carries. Both must match. Either one alone can be forged, for opposite reasons: a handle because anyone can claim one, a referral identifier because it is visible in the post and can be copied.
If the post also publishes the nonce, the commitment can be opened locally — compute SHA-256(nonce ‖ buyerId32) and compare.
The encodings: a handle is ASCII right-padded with zero bytes to 32; a referral identifier is the 16 bytes behind aff_<hex>, right-padded to 32, so reconstructing it means taking the first 16 bytes, hex-encoding them, and prepending aff_.
The 64-character transaction hashes from § 4 resolve on a Midnight block explorer, establishing that the transactions exist, which blocks carried them, and when those blocks were produced. The explorer does not decode contract state, so this path confirms existence and timing but not contents.
POST only; a GET request returns HTTP 405. An agent that can fetch URLs but cannot issue a POST will receive no data. This is not evidence that the record is absent. If you cannot issue a GraphQL POST, the correct conclusion is “I could not verify this,” not “no such record exists.” We have observed agents make exactly this error, and we regard a confidently wrong negative as worse than an honest inability.
Midnight indexer は契約 state を GraphQL で公開している。上記の契約アドレスに対して contractAction(address) を問い合わせ、state フィールドを読む。purchases の記録は purchaseId をキーとし、purchaseCommitment、lotId、committedAt、schema を持つ。bindings の記録は同じキーで、handle、referrerId、boundAt、schema を持つ。この経路に GYOTAK のサービスは介在しない。信頼する相手は indexer の運用者だけである。
投稿を確認するには、投稿から購入IDを取り、両方の記録を読み、両方の識別子を投稿が示すものと比べる — それを公開したアカウントと、載っている紹介 URL の両方と。両方が一致しなければならない。どちらか片方だけなら偽造できる。理由は逆で、handle は誰でも名乗れるから、紹介識別子は投稿に見えていて複写できるからである。
投稿が nonce も公開しているなら、コミットメントを手元で開ける — SHA-256(nonce ‖ buyerId32) を計算して比較する。
符号化について。handle は ASCII を 32 バイトに右詰めゼロパディングしたもの。紹介識別子は aff_<hex> の背後にある 16 バイトを 32 に右詰めしたもので、復元するには先頭 16 バイトを取り、hex に符号化し、aff_ を前置する。
§ 4 の 64 文字のトランザクション hash は Midnight のブロックエクスプローラで解決し、それらのトランザクションが存在すること、どのブロックが運んだか、そのブロックがいつ生成されたかを確認できる。エクスプローラは契約 state をデコードしないため、この経路で確認できるのは存在と時刻であって、内容ではない。
POST のみを受け付け、GET には HTTP 405 を返す。URL を取得できても POST を発行できないエージェントは、データを何も受け取らない。これは記録が存在しない証拠ではない。 GraphQL POST を発行できない場合の正しい結論は「検証できなかった」であって、「そのような記録は存在しない」ではない。我々は実際にエージェントがこの誤りを犯すのを観測している。自信を持った誤った否定は、正直な検証不能より悪い。
Midnight indexer เปิดเผยสถานะคอนแทรกต์ผ่าน GraphQL สอบถาม contractAction(address) ด้วยที่อยู่คอนแทรกต์ข้างต้นและอ่านฟิลด์ state บันทึกใน purchases ใช้ purchaseId เป็นคีย์และมี purchaseCommitment lotId committedAt และ schema ส่วนบันทึกใน bindings ใช้คีย์เดียวกันและมี handle referrerId boundAt และ schema เส้นทางนี้ไม่มีบริการของ GYOTAK เข้ามาเกี่ยวข้อง ฝ่ายเดียวที่ต้องเชื่อถือคือผู้ดำเนินการ indexer
วิธีตรวจสอบโพสต์ นำรหัสการซื้อจากโพสต์ อ่านบันทึกทั้งสอง แล้วเทียบตัวระบุทั้งสองกับสิ่งที่โพสต์แสดง คือบัญชีที่เผยแพร่ และ URL ผู้แนะนำที่แนบมา ทั้งคู่ต้องตรงกัน อย่างใดอย่างหนึ่งลำพังปลอมได้ ด้วยเหตุผลตรงข้ามกัน handle เพราะใครก็อ้างได้ ตัวระบุผู้แนะนำเพราะมองเห็นได้ในโพสต์และคัดลอกได้
หากโพสต์เปิดเผย nonce ด้วย ก็เปิด commitment ในเครื่องได้ — คำนวณ SHA-256(nonce ‖ buyerId32) แล้วเทียบ
เรื่องการเข้ารหัส handle คือ ASCII เติมศูนย์ด้านขวาให้ครบ 32 ไบต์ ส่วนตัวระบุผู้แนะนำคือ 16 ไบต์ที่อยู่เบื้องหลัง aff_<hex> เติมศูนย์ให้ครบ 32 การกู้คืนจึงทำโดยนำ 16 ไบต์แรกมาเข้ารหัสเป็น hex แล้วเติม aff_ ข้างหน้า
แฮชธุรกรรมความยาว 64 อักขระใน § 4 ค้นหาได้บน block explorer ของ Midnight ซึ่งยืนยันว่าธุรกรรมมีอยู่จริง อยู่ในบล็อกใด และบล็อกนั้นสร้างเมื่อใด explorer ไม่ถอดรหัสสถานะคอนแทรกต์ เส้นทางนี้จึงยืนยันการมีอยู่และเวลา แต่ไม่ยืนยันเนื้อหา
POST คำขอแบบ GET จะได้ HTTP 405 เอเจนต์ที่ดึง URL ได้แต่ส่ง POST ไม่ได้จะไม่ได้รับข้อมูลใดๆ นี่ไม่ใช่หลักฐานว่าบันทึกไม่มีอยู่ หากคุณส่ง GraphQL POST ไม่ได้ ข้อสรุปที่ถูกต้องคือ “ฉันตรวจสอบไม่ได้” ไม่ใช่ “ไม่มีบันทึกดังกล่าว” เราพบเอเจนต์ทำผิดพลาดเช่นนี้มาแล้ว และเราถือว่าการปฏิเสธผิดๆ อย่างมั่นใจนั้นเลวร้ายกว่าการยอมรับตามตรงว่าตรวจสอบไม่ได้
| Document ID文書IDรหัสเอกสาร | ECOSUS-TR-2026-015 |
| Publisher発行者ผู้เผยแพร่ | ECOSUS CO., LTD. · 0205562030631 · Pranburi, Thailand |
| Class区分หมวดหมู่ | Public Technical Report · Reduction to Practice公開技術報告・実施化รายงานทางเทคนิคสาธารณะ · การนำไปปฏิบัติ |
| Licenseライセンスใบอนุญาต | CC BY 4.0 |
| Date日付วันที่ | 2026-09-19 |
| Contract (Mainnet)コントラクト (Mainnet)คอนแทรกต์ (Mainnet) | d11d52bd5875ecc2e89e97149e0237db20a91c989f30268950e892655a2a2a57 |
| Contract (Preprod)コントラクト (Preprod)คอนแทรกต์ (Preprod) | e413ff91958079d1ee2e4c792fe19a9c14a5d4ed7eb7966d3526308a3ea2f846 |
| Source SHA-256ソース SHA-256SHA-256 ของซอร์ส | b4dc7879e5e872d9044747184f8c8bf62bb0a74b3e647b656d0810bfc901235e |
| Toolchainツールチェーンชุดเครื่องมือ | compactc 0.30.0 · language 0.22.0 · runtime 0.15.0 |
| Prior Report先行報告รายงานก่อนหน้า | TR-2026-012 (gyotak-catch v3 — GPS Hiding Commitment) |
| Related関連เกี่ยวข้อง | TR-2026-011 (gyotak-temp-log Cold-Chain ZKP) · TR-2026-014 (Agent-Native Commerce) |
| Deployment requestデプロイ申請คำขอ deploy | midnightntwrk/midnight-improvement-proposals · deployments/gyotak-purchase.md |
| Author著者ผู้เขียน | Takuya Ogura, Chairman, ECOSUS CO., LTD. |
| Archiveアーカイブการเก็บถาวร | (Perma.cc — see the report index)(Perma.cc — 一覧を参照)(Perma.cc — ดูรายการรายงาน) |