Przejdź do głównej zawartości

Korzystanie z ukrytych adresów

Ukryty adres
prywatność
kryptografia
Rust
wasm
Średnio zaawansowany
Ori Pomerantz
30 listopada 2025
14 minuta czytania

Jesteś Billem. Z powodów, których nie będziemy tu omawiać, chcesz przekazać darowiznę na kampanię „Alicja na królową świata” i chcesz, aby Alicja wiedziała, że to Ty przekazałeś darowiznę, aby mogła Cię nagrodzić, jeśli wygra. Niestety, jej zwycięstwo nie jest gwarantowane. Istnieje konkurencyjna kampania „Karolina na cesarzową Układu Słonecznego”. Jeśli Karolina wygra i dowie się, że wsparłeś Alicję, będziesz w kłopotach. Więc nie możesz po prostu przelać 200 ETH ze swojego konta na konto Alicji.

Rozwiązaniem jest ERC-5564 (opens in a new tab). Ten ERC wyjaśnia, jak używać ukrytych adresów (opens in a new tab) do anonimowych transferów.

Ostrzeżenie: Kryptografia stojąca za ukrytymi adresami jest, o ile nam wiadomo, solidna. Istnieją jednak potencjalne ataki typu side-channel. Poniżej zobaczysz, co możesz zrobić, aby zmniejszyć to ryzyko.

Jak działają ukryte adresy

Ten artykuł spróbuje wyjaśnić działanie ukrytych adresów na dwa sposoby. Pierwszy to jak z nich korzystać. Ta część jest wystarczająca do zrozumienia reszty artykułu. Następnie znajduje się wyjaśnienie matematyki stojącej za tym mechanizmem. Jeśli interesujesz się kryptografią, przeczytaj również tę część.

Wersja prosta (jak używać ukrytych adresów)

Alicja tworzy dwa klucze prywatne i publikuje odpowiadające im klucze publiczne (które można połączyć w jeden meta-adres o podwójnej długości). Bill również tworzy klucz prywatny i publikuje odpowiadający mu klucz publiczny.

Używając klucza publicznego jednej strony i klucza prywatnego drugiej, można uzyskać wspólny sekret znany tylko Alicji i Billowi (nie można go uzyskać z samych kluczy publicznych). Używając tego wspólnego sekretu, Bill uzyskuje ukryty adres i może na niego wysyłać aktywa.

Alicja również uzyskuje adres ze wspólnego sekretu, ale ponieważ zna klucze prywatne do opublikowanych przez siebie kluczy publicznych, może również uzyskać klucz prywatny, który pozwala jej wypłacić środki z tego adresu.

Matematyka (dlaczego ukryte adresy działają w ten sposób)

Standardowe ukryte adresy używają kryptografii krzywych eliptycznych (ECC) (opens in a new tab), aby uzyskać lepszą wydajność przy mniejszej liczbie bitów klucza, zachowując jednocześnie ten sam poziom bezpieczeństwa. Jednak w większości możemy to zignorować i udawać, że używamy zwykłej arytmetyki.

Istnieje liczba, którą wszyscy znają, G. Można mnożyć przez G. Ale ze względu na naturę ECC, praktycznie niemożliwe jest dzielenie przez G. Sposób, w jaki ogólnie działa kryptografia klucza publicznego w Ethereum, polega na tym, że można użyć klucza prywatnego, Ppriv, do podpisywania transakcji, które są następnie weryfikowane przez klucz publiczny, Ppub = GPpriv.

Alicja tworzy dwa klucze prywatne, Kpriv i Vpriv. Kpriv będzie używany do wydawania pieniędzy z ukrytego adresu, a Vpriv do przeglądania adresów należących do Alicji. Alicja następnie publikuje klucze publiczne: Kpub = GKpriv i Vpub = GVpriv

Bill tworzy trzeci klucz prywatny, Rpriv, i publikuje Rpub = GRpriv w centralnym rejestrze (Bill mógłby również wysłać go do Alicji, ale zakładamy, że Karolina podsłuchuje).

Bill oblicza RprivVpub = GRprivVpriv, co, jak oczekuje, Alicja również zna (wyjaśniono poniżej). Ta wartość nazywana jest S, wspólnym sekretem. Daje to Billowi klucz publiczny, Ppub = Kpub+G*hasz(S). Na podstawie tego klucza publicznego może obliczyć adres i wysłać na niego dowolne zasoby. W przyszłości, jeśli Alicja wygra, Bill może podać jej Rpriv, aby udowodnić, że zasoby pochodzą od niego.

Alicja oblicza RpubVpriv = GRprivVpriv. Daje jej to ten sam wspólny sekret, S. Ponieważ zna klucz prywatny, Kpriv, może obliczyć Ppriv = Kpriv+hasz(S). Ten klucz pozwala jej na dostęp do aktywów pod adresem wynikającym z Ppub = GPpriv = GKpriv+G*hasz(S) = Kpub+G*hasz(S).

Mamy oddzielny klucz do przeglądania, aby umożliwić Alicji zlecenie usług firmie Dave's World Domination Campaign Services. Alicja jest skłonna pozwolić Dave'owi poznać adresy publiczne i informować ją, gdy dostępne będą kolejne pieniądze, ale nie chce, aby wydawał on pieniądze z jej kampanii.

Ponieważ przeglądanie i wydawanie używają oddzielnych kluczy, Alicja może dać Dave'owi Vpriv. Wtedy Dave może obliczyć S = RpubVpriv = GRprivVpriv i w ten sposób uzyskać klucze publiczne (Ppub = Kpub+G*hasz(S)). Ale bez Kpriv Dave nie może uzyskać klucza prywatnego.

Podsumowując, oto wartości znane przez różnych uczestników.

AlicjaOpublikowaneBillDave
GGGG
Kpriv
VprivVpriv
Kpub = GKprivKpubKpubKpub
Vpub = GVprivVpubVpubVpub
Rpriv
RpubRpubRpub = GRprivRpub
S = RpubVpriv = GRprivVprivS = RprivVpub = GRprivVprivS = RpubVpriv = GRprivVpriv
Ppub = Kpub+G*hasz(S)Ppub = Kpub+G*hasz(S)Ppub = Kpub+G*hasz(S)
Adres=f(Ppub)Adres=f(Ppub)Adres=f(Ppub)Adres=f(Ppub)
Ppriv = Kpriv+hasz(S)

Kiedy ukryte adresy zawodzą

Na blockchainie nie ma żadnych tajemnic. Chociaż ukryte adresy mogą zapewnić Ci prywatność, jest ona podatna na analizę ruchu. Jako trywialny przykład wyobraź sobie, że Bill zasila adres i natychmiast wysyła transakcję w celu opublikowania wartości Rpub. Bez Vpriv Alicji nie możemy być pewni, że jest to ukryty adres, ale tak należy zakładać. Następnie widzimy inną transakcję, która przenosi całe ETH z tego adresu na adres funduszu kampanii Alicji. Możemy nie być w stanie tego udowodnić, ale jest prawdopodobne, że Bill właśnie przekazał darowiznę na kampanię Alicji. Karolina z pewnością by tak pomyślała.

Bill może łatwo oddzielić publikację Rpub od zasilenia ukrytego adresu (wykonać je w różnym czasie, z różnych adresów). To jednak nie wystarczy. Wzorzec, którego szuka Karolina, polega na tym, że Bill zasila adres, a następnie fundusz kampanii Alicji wypłaca z niego środki.

Jednym z rozwiązań jest to, aby kampania Alicji nie wypłacała pieniędzy bezpośrednio, ale używała ich do zapłaty stronie trzeciej. Jeśli kampania Alicji wyśle 10 ETH do Dave's World Domination Campaign Services, Karolina będzie wiedziała tylko, że Bill przekazał darowiznę jednemu z klientów Dave'a. Jeśli Dave ma wystarczającą liczbę klientów, Karolina nie będzie w stanie stwierdzić, czy Bill przekazał darowiznę Alicji, która z nią konkuruje, czy Adamowi, Albertowi lub Abigail, na których Karolinie nie zależy. Alicja może dołączyć do płatności zahaszowaną wartość, a następnie dostarczyć Dave'owi jej preobraz, aby udowodnić, że była to jej darowizna. Alternatywnie, jak wspomniano powyżej, jeśli Alicja da Dave'owi swoje Vpriv, on już wie, od kogo pochodzi płatność.

Głównym problemem tego rozwiązania jest to, że wymaga ono od Alicji dbania o tajemnicę, gdy ta tajemnica przynosi korzyści Billowi. Alicja może chcieć utrzymać swoją reputację, aby przyjaciel Billa, Bob, również przekazał jej darowiznę. Ale możliwe jest również, że nie będzie jej przeszkadzało zdemaskowanie Billa, ponieważ wtedy będzie się on bał, co się stanie, jeśli Karolina wygra. Bill może w końcu udzielić Alicji jeszcze większego wsparcia.

Używanie wielu warstw ukrytych

Zamiast polegać na Alicji w kwestii zachowania prywatności Billa, Bill może zrobić to sam. Może wygenerować wiele meta-adresów dla fikcyjnych osób, Boba i Belli. Następnie Bill wysyła ETH do Boba, a „Bob” (który w rzeczywistości jest Billem) wysyła je do Belli. „Bella” (również Bill) wysyła je do Alicji.

Karolina wciąż może przeprowadzić analizę ruchu i zobaczyć potok od Billa do Boba, do Belli, do Alicji. Jednakże, jeśli „Bob” i „Bella” również używają ETH do innych celów, nie będzie wyglądało na to, że Bill przekazał cokolwiek Alicji, nawet jeśli Alicja natychmiast wypłaci środki z ukrytego adresu na swój znany adres kampanii.

Pisanie aplikacji z ukrytymi adresami

Ten artykuł wyjaśnia aplikację z ukrytymi adresami dostępną na GitHub (opens in a new tab).

Narzędzia

Istnieje biblioteka ukrytych adresów w TypeScript (opens in a new tab), której moglibyśmy użyć. Jednak operacje kryptograficzne mogą być intensywne dla procesora. Wolę je implementować w języku kompilowanym, takim jak Rust (opens in a new tab), i używać WASM (opens in a new tab) do uruchamiania kodu w przeglądarce.

Będziemy używać Vite (opens in a new tab) i React (opens in a new tab). Są to standardowe narzędzia branżowe; jeśli ich nie znasz, możesz skorzystać z tego samouczka. Aby używać Vite, potrzebujemy Node.

Zobacz ukryte adresy w akcji

  1. Zainstaluj niezbędne narzędzia: Rust (opens in a new tab) i Node (opens in a new tab).

  2. Sklonuj repozytorium GitHub.

    1git clone https://github.com/qbzzt/251022-stealth-addresses.git
    2cd 251022-stealth-addresses
  3. Zainstaluj wymagania wstępne i skompiluj kod Rust.

    1cd src/rust-wasm
    2rustup target add wasm32-unknown-unknown
    3cargo install wasm-pack
    4wasm-pack build --target web
  4. Uruchom serwer WWW (opens in a new tab).

    1cd ../..
    2npm install
    3npm run dev
  5. Przejdź do aplikacji (opens in a new tab). Ta strona aplikacji ma dwie ramki: jedną dla interfejsu użytkownika Alicji, a drugą dla Billa. Te dwie ramki nie komunikują się ze sobą; znajdują się na tej samej stronie tylko dla wygody.

  6. Jako Alicja kliknij Wygeneruj ukryty meta-adres. Wyświetli to nowy ukryty adres i odpowiadające mu klucze prywatne. Skopiuj ukryty meta-adres do schowka.

  7. Jako Bill, wklej nowy ukryty meta-adres i kliknij Wygeneruj adres. Otrzymasz adres do zasilenia dla Alicji.

  8. Skopiuj adres i klucz publiczny Billa i wklej je w polu „Klucz prywatny dla adresu wygenerowanego przez Billa” w interfejsie użytkownika Alicji. Po wypełnieniu tych pól zobaczysz klucz prywatny umożliwiający dostęp do aktywów pod tym adresem.

  9. Możesz użyć kalkulatora online (opens in a new tab), aby upewnić się, że klucz prywatny odpowiada adresowi.

Jak działa program

Komponent WASM

Kod źródłowy, który kompiluje się do WASM, jest napisany w języku Rust (opens in a new tab). Możesz go zobaczyć w src/rust_wasm/src/lib.rs (opens in a new tab). Ten kod jest przede wszystkim interfejsem między kodem JavaScript a biblioteką eth-stealth-addresses (opens in a new tab).

Cargo.toml

Cargo.toml (opens in a new tab) w Rust jest analogiczny do package.json (opens in a new tab) w JavaScript. Zawiera informacje o pakiecie, deklaracje zależności itp.

1[package]
2name = "rust-wasm"
3version = "0.1.0"
4edition = "2024"
5
6[dependencies]
7eth-stealth-addresses = "0.1.0"
8hex = "0.4.3"
9wasm-bindgen = "0.2.104"
10getrandom = { version = "0.2", features = ["js"] }
Pokaż wszystko

Pakiet getrandom (opens in a new tab) musi generować wartości losowe. Nie można tego zrobić czysto algorytmicznymi środkami; wymaga to dostępu do procesu fizycznego jako źródła entropii. Ta definicja określa, że uzyskamy tę entropię, pytając przeglądarkę, w której działamy.

1console_error_panic_hook = "0.1.7"

Ta biblioteka (opens in a new tab) dostarcza nam bardziej znaczących komunikatów o błędach, gdy kod WASM wpadnie w panikę i nie może kontynuować.

1[lib]
2crate-type = ["cdylib", "rlib"]

Typ wyjściowy wymagany do wyprodukowania kodu WASM.

lib.rs

To jest właściwy kod Rust.

1use wasm_bindgen::prelude::*;

Definicje do stworzenia pakietu WASM z Rusta. Są one udokumentowane tutaj (opens in a new tab).

1use eth_stealth_addresses::{
2 generate_stealth_meta_address,
3 generate_stealth_address,
4 compute_stealth_key
5};

Funkcje, których potrzebujemy z biblioteki eth-stealth-addresses (opens in a new tab).

1use hex::{decode,encode};

Rust zazwyczaj używa tablic bajtów (arrays (opens in a new tab)) ([u8; <rozmiar>]) dla wartości. Ale w JavaScript zazwyczaj używamy ciągów szesnastkowych. Biblioteka hex (opens in a new tab) tłumaczy dla nas z jednej reprezentacji na drugą.

1#[wasm_bindgen]

Generuj powiązania WASM, aby móc wywołać tę funkcję z JavaScript.

1pub fn wasm_generate_stealth_meta_address() -> String {

Najprostszym sposobem na zwrócenie obiektu z wieloma polami jest zwrócenie ciągu znaków JSON.

1 let (address, spend_private_key, view_private_key) =
2 generate_stealth_meta_address();

Funkcja generate_stealth_meta_address (opens in a new tab) zwraca trzy pola:

  • Meta-adres (Kpub i Vpub)
  • Klucz prywatny do przeglądania (Vpriv)
  • Klucz prywatny do wydawania (Kpriv)

Składnia tupli (opens in a new tab) pozwala nam ponownie rozdzielić te wartości.

1 format!("{{\"address\":\"{}\",\"view_private_key\":\"{}\",\"spend_private_key\":\"{}\"}}",
2 encode(address),
3 encode(view_private_key),
4 encode(spend_private_key)
5 )
6}

Użyj makra format! (opens in a new tab), aby wygenerować ciąg znaków w formacie JSON. Użyj hex::encode (opens in a new tab), aby zamienić tablice na ciągi szesnastkowe.

1fn str_to_array<const N: usize>(s: &str) -> Option<[u8; N]> {

Ta funkcja zamienia ciąg szesnastkowy (dostarczony przez JavaScript) na tablicę bajtów. Używamy jej do parsowania wartości dostarczonych przez kod JavaScript. Ta funkcja jest skomplikowana ze względu na sposób, w jaki Rust obsługuje tablice i wektory.

Wyrażenie <const N: usize> nazywane jest generykiem (opens in a new tab). N jest parametrem, który kontroluje długość zwracanej tablicy. Funkcja jest w rzeczywistości wywoływana jako str_to_array::<n>, gdzie n to długość tablicy.

Wartość zwracana to Option<[u8; N]>, co oznacza, że zwracana tablica jest opcjonalna (opens in a new tab). Jest to typowy wzorzec w Rust dla funkcji, które mogą zakończyć się niepowodzeniem.

Na przykład, jeśli wywołamy str_to_array::10("bad060a7"), funkcja ma zwrócić tablicę dziesięciu wartości, ale dane wejściowe mają tylko cztery bajty. Funkcja musi zakończyć się niepowodzeniem i robi to, zwracając None. Wartością zwrotną dla str_to_array::4("bad060a7") byłoby Some<[0xba, 0xd0, 0x60, 0xa7]>.

1 // decode returns Result<Vec<u8>, _>
2 let vec = decode(s).ok()?;

Funkcja hex::decode (opens in a new tab) zwraca Result<Vec<u8>, FromHexError>. Typ Result (opens in a new tab) może zawierać pomyślny wynik (Ok(value)) lub błąd (Err(error)).

Metoda .ok() zamienia Result na Option, której wartością jest albo wartość Ok(), jeśli operacja się powiedzie, albo None, jeśli nie. Na koniec, operator znaku zapytania (opens in a new tab) przerywa bieżącą funkcję i zwraca None, jeśli Option jest puste. W przeciwnym razie odpakowuje wartość i zwraca ją (w tym przypadku, aby przypisać wartość do vec).

Wygląda to na dziwnie zawiłą metodę obsługi błędów, ale Result i Option zapewniają, że wszystkie błędy są obsługiwane, w ten czy inny sposób.

1 if vec.len() != N { return None; }

Jeśli liczba bajtów jest nieprawidłowa, jest to błąd i zwracamy None.

1 // try_into consumes vec and attempts to make [u8; N]
2 let array: [u8; N] = vec.try_into().ok()?;

Rust ma dwa typy tablic. Tablice (opens in a new tab) mają stały rozmiar. Wektory (opens in a new tab) mogą rosnąć i maleć. hex::decode zwraca wektor, ale biblioteka eth_stealth_addresses chce otrzymywać tablice. .try_into() (opens in a new tab) konwertuje wartość na inny typ, na przykład wektor na tablicę.

1 Some(array)
2}

Rust nie wymaga użycia słowa kluczowego return (opens in a new tab) przy zwracaniu wartości na końcu funkcji.

1#[wasm_bindgen]
2pub fn wasm_generate_stealth_address(stealth_address: &str) -> Option<String> {

Ta funkcja otrzymuje publiczny meta-adres, który zawiera zarówno Vpub, jak i Kpub. Zwraca ukryty adres, klucz publiczny do opublikowania (Rpub) oraz jednobajtową wartość skanowania, która przyspiesza identyfikację, które opublikowane adresy mogą należeć do Alicji.

Wartość skanowania jest częścią wspólnego sekretu (S = GRprivVpriv). Ta wartość jest dostępna dla Alicji, a jej sprawdzenie jest znacznie szybsze niż sprawdzenie, czy f(Kpub+G*hasz(S)) jest równe opublikowanemu adresowi.

1 let (address, r_pub, scan) =
2 generate_stealth_address(&str_to_array::<66>(stealth_address)?);

Używamy biblioteki generate_stealth_address (opens in a new tab).

1 format!("{{\"address\":\"{}\",\"rPub\":\"{}\",\"scan\":\"{}\"}}",
2 encode(address),
3 encode(r_pub),
4 encode(&[scan])
5 ).into()
6}

Przygotuj ciąg wyjściowy zakodowany w formacie JSON.

1#[wasm_bindgen]
2pub fn wasm_compute_stealth_key(
3 address: &str,
4 bill_pub_key: &str,
5 view_private_key: &str,
6 spend_private_key: &str
7) -> Option<String> {
8 .
9 .
10 .
11}
Pokaż wszystko

Ta funkcja używa biblioteki compute_stealth_key (opens in a new tab) do obliczenia klucza prywatnego do wypłaty z adresu (Rpriv). To obliczenie wymaga następujących wartości:

  • Adres (Adres=f(Ppub))
  • Klucz publiczny wygenerowany przez Billa (Rpub)
  • Klucz prywatny do przeglądania (Vpriv)
  • Klucz prywatny do wydawania (Kpriv)
1#[wasm_bindgen(start)]

#[wasm_bindgen(start)] (opens in a new tab) określa, że funkcja jest wykonywana po zainicjowaniu kodu WASM.

1pub fn main() {
2 console_error_panic_hook::set_once();
3}

Ten kod określa, że dane wyjściowe paniki są wysyłane do konsoli JavaScript. Aby zobaczyć to w działaniu, użyj aplikacji i podaj Billowi nieprawidłowy meta-adres (wystarczy zmienić jedną cyfrę szesnastkową). W konsoli JavaScript zobaczysz następujący błąd:

1rust_wasm.js:236 panicked at /home/ori/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/subtle-2.6.1/src/lib.rs:701:9:
2assertion `left == right` failed
3 left: 0
4 right: 1

Po którym następuje ślad stosu. Następnie podaj Billowi prawidłowy meta-adres, a Alicji nieprawidłowy adres lub nieprawidłowy klucz publiczny. Zobaczysz następujący błąd:

1rust_wasm.js:236 panicked at /home/ori/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/eth-stealth-addresses-0.1.0/src/lib.rs:78:9:
2klucze nie generują ukrytego adresu

Ponownie, po którym następuje ślad stosu.

Interfejs użytkownika

Interfejs użytkownika jest napisany przy użyciu React (opens in a new tab) i serwowany przez Vite (opens in a new tab). Możesz dowiedzieć się o nich, korzystając z tego samouczka. Nie ma tu potrzeby używania WAGMI (opens in a new tab), ponieważ nie wchodzimy w bezpośrednią interakcję z blockchainem ani portfelem.

Jedyną nieoczywistą częścią interfejsu użytkownika jest łączność z WASM. Oto jak to działa.

vite.config.js

Ten plik zawiera konfigurację Vite (opens in a new tab).

1import { defineConfig } from 'vite'
2import react from '@vitejs/plugin-react'
3import wasm from "vite-plugin-wasm";
4
5// https://vite.dev/config/
6export default defineConfig({
7 plugins: [react(), wasm()],
8})

Potrzebujemy dwóch wtyczek Vite: react (opens in a new tab) i wasm (opens in a new tab).

App.jsx

Ten plik jest głównym komponentem aplikacji. Jest to kontener, który zawiera dwa komponenty: Alice i Bill, interfejsy użytkownika dla tych użytkowników. Istotną częścią dla WASM jest kod inicjalizacyjny.

1import init from './rust-wasm/pkg/rust_wasm.js'

Gdy używamy wasm-pack (opens in a new tab), tworzy on dwa pliki, których tu używamy: plik wasm z właściwym kodem (tutaj, src/rust-wasm/pkg/rust_wasm_bg.wasm) oraz plik JavaScript z definicjami do jego użycia (tutaj, src/rust_wasm/pkg/rust_wasm.js). Domyślny eksport tego pliku JavaScript to kod, który musi zostać uruchomiony, aby zainicjować WASM.

1function App() {
2 .
3 .
4 .
5 useEffect(() => {
6 const loadWasm = async () => {
7 try {
8 await init();
9 setWasmReady(true)
10 } catch (err) {
11 console.error('Error loading wasm:', err)
12 alert("Wasm error: " + err)
13 }
14 }
15
16 loadWasm()
17 }, []
18 )
Pokaż wszystko

Hak useEffect (opens in a new tab) pozwala określić funkcję, która jest wykonywana, gdy zmieniają się zmienne stanu. Tutaj lista zmiennych stanu jest pusta ([]), więc ta funkcja jest wykonywana tylko raz, gdy strona się ładuje.

Funkcja efektu musi natychmiast zwrócić wartość. Aby użyć kodu asynchronicznego, takiego jak init WASM (który musi załadować plik .wasm i dlatego wymaga czasu), definiujemy wewnętrzną funkcję async (opens in a new tab) i uruchamiamy ją bez await.

Bill.jsx

To jest interfejs użytkownika dla Billa. Ma jedną akcję: tworzenie adresu na podstawie ukrytego meta-adresu dostarczonego przez Alicję.

1import { wasm_generate_stealth_address } from './rust-wasm/pkg/rust_wasm.js'

Oprócz domyślnego eksportu, kod JavaScript wygenerowany przez wasm-pack eksportuje funkcję dla każdej funkcji w kodzie WASM.

1 <button onClick={() => {
2 setPublicAddress(JSON.parse(wasm_generate_stealth_address(stealthMetaAddress)))
3 }}>

Aby wywołać funkcje WASM, po prostu wywołujemy funkcję wyeksportowaną przez plik JavaScript utworzony przez wasm-pack.

Alice.jsx

Kod w Alice.jsx jest analogiczny, z wyjątkiem tego, że Alicja ma dwie akcje:

  • Wygeneruj meta-adres
  • Uzyskaj klucz prywatny dla adresu opublikowanego przez Billa

Wnioski

Ukryte adresy nie są panaceum; muszą być używane poprawnie. Ale gdy są używane poprawnie, mogą zapewnić prywatność na publicznym blockchainie.

Zobacz więcej mojej pracy tutaj (opens in a new tab).

Strona ostatnio zaktualizowana: 14 listopada 2025

Czy ten samouczek był pomocny?