Solidus Bridge testnet ağında çalışıyor. Burada görünen her şey test verisidir, parasal değeri yoktur.

Hedef, imzalanan şeyin içindedir; bu yüzden bir imza başka bir zincirde yeniden kullanılamaz

Solidus Bridge bir kimlik belgesinin durumunu başka zincirlere kopyalar ve her zincirdeki bir sözleşme, bir durumu yalnızca yeterli sayıda doğrulayıcı imzaladıysa kabul eder. Kimlik belgesi durumu köprüsü nedir sayfası yolun tamamını kapsar. Veriyi görmeden bir koşulu denetleyin sayfası, uygulanmış bir durumla bir sözleşmenin ne yaptığını kapsar. Bu sayfa yoldaki tek bir kuralı kapsar: bir imza bir hedef için geçerlidir, başka hiçbiri için değil.

Tekrar saldırısı, tek bir örnekle

Tekrar (replay), gerçek ve geçerli bir mesajın sayılmaması gereken bir yerde yeniden gönderilmesidir. İmza gerçektir. Yer ya da an yanlıştır.

Diyelim ki doğrulayıcılar Sepolia için 41 numaralı mesajı imzaladı. Biri onu imzalarıyla birlikte, yine 41 numaralı bir mesaj bekleyen bir Fuji sözleşmesine kopyalıyor. İmza yalnızca mesaj baytlarını kapsasaydı Fuji, güvendiği imzalayanlardan gelen geçerli imzalar görür ve kopyayı kabul ederdi. Hiçbir şey sahte değildi. İmzalayanlar hiçbir zaman Fuji'yi kastetmedi.

Savunma, hedefi doğrulayıcıların imzaladığı şeyin içine koymaktır.

İmzalanan baytların içinde ne var

Doğrulayıcılar mesajın kendisini imzalamaz. Sabit bir etiketin ve beş değerin özetini imzalar. Bu tablo AttestationLib.digest ve SolidusCredentialMirror.submit kodundan okunmuştur.

Alan Genişlik Ayna bunu nereden alır
Etiket SOLIDUS_BRIDGE_ATTESTATION_V1 29 bayt Kütüphanedeki bir sabit
Solidus zincir kimliği 8 bayt Dağıtım sırasında aynada saklanır
Hedef alan 4 bayt Dağıtım sırasında aynada localDomain olarak saklanır
Sıra numarası 8 bayt Mesaj başlığı
Mesaj kimliği 32 bayt Mesajın tamamının keccak256 hash'i
Solidus yüksekliği 8 bayt Mesaj başlığı

Değerler art arda paketlenir, toplam 89 bayt, ve keccak256 ile hash'lenir. Sonuç standart Ethereum imzalı mesaj önekiyle sarılır, yeniden hash'lenir ve imzalanır. Her imza 65 bayttır.

Mesaj kimliği gövdenin tamamını hash'ler; bu yüzden gövde imzaların altından değiştirilemez. 18 baytlık mesaj başlığında (sürüm, tür, sıra, yükseklik) hedef alanı yoktur. Hedef mesajın içinde değildir. Ayna kendi hedefini getirir.

Hedef neden önemli

Biri submit(message, signatures) çağırdığında ayna özeti kendi zincirinde, kendi localDomain değeriyle yeniden kurar. Sonra her imza üzerinde ecrecover çalıştırır. Bu işlev bir hash ve bir imza alır ve imzayı yapan adresi döndürür. Hash'i değiştirin, aynı imza farklı bir adres döndürür. Ayna, geri kazanılan her adresi kendi doğrulayıcı listesiyle karşılaştırır.

Dolayısıyla Fuji aynasına gösterilen bir Sepolia imzası bir Fuji özetine karşı denetlenir. Hiçbir doğrulayıcının sahip olmadığı bir adres çıkar ve çağrı UnknownSigner ile geri alınır.

2026-10-08 tarihinde, dört imzasıyla tek bir Sepolia mesajında (bir kalp atışı, sıra 1274) ölçüldü: Sepolia alanıyla yeniden kurulduğunda geri kazanılan dört adresin hepsi listelenen imzalayanlarla eşleşti. Fuji alanıyla ve bir kez de Arc alanıyla yeniden kurulduğunda, geri kazanılan sekiz adresin hiçbiri listelenen bir imzalayanla eşleşmedi.

Bağ gerçek bir iş yapar. Aynı gün Sepolia ve Fuji aynaları aynı dört doğrulayıcı adresini listeledi ve /v1/routes Sepolia, Fuji ve Arc için tek bir ayna adresi gösteriyor. Ne imzalayanlar ne sözleşme adresi zincirleri birbirinden ayırır. Ayıran, özetteki alandır.

Ayna, sırayı herhangi bir imzadan önce denetler. 2026-10-08 tarihinde saat 16:34 UTC'de Sepolia 1278'i, Fuji 1016'yı bekliyordu; bu yüzden kopyalanmış bir mesaj Fuji'de çoğunlukla önce OutOfSequence hatasında dururdu. Alan bağı, sayıların tesadüfen tuttuğu durumda koruyan şeydir.

Sıra ve yinelemeler

Sıkı sıra. Her ayna nextDomainSeq değerini tutar. Bir mesajı yalnızca mesajın sırası bu sayıya tam olarak eşitse uygular. Başka her şey OutOfSequence(expected, got) ile geri alınır. Bir mesaj uygulandıktan sonra sayaç bir artar. Bir mesaj iki kez uygulanamaz ve 42 numaralı mesaj 41 numaralıdan önce uygulanamaz. 41 bir iptal taşıyorsa hiçbir şey onu atlayamaz. Bir geri alma her yazmayı geri alır, bu yüzden reddedilen bir mesaj bir yuvayı tüketmez. Her hedef kendi sayısını tutar.

Artan imzalayan sırası. İmzalar tek bir bayt dizisi olarak gelir. Ayna, geri kazanılan her adresin bir öncekinden kesin olarak büyük olmasını ister, yoksa SignersOutOfOrder ile geri alır. Bir tekrar kendinden büyük olamaz; bu yüzden bir doğrulayıcı iki kez sayılamaz. Kod yorumu nedeni veriyor: tek bir anahtar aynı özet üzerinde birçok farklı geçerli imza dizisi üretebilir ve sıra kuralı bunların birçok imzalayan gibi geçmesini engeller; kurulacak bir liste ya da küme de gerekmez. Ayna ayrıca bir imzanın üst yarı biçimini (HighS), yani aynı imzanın ikinci bir yazılışını da reddeder. Daha fazla denetim güvenlik sayfasında.

Tek komutla görün

Mesaj listesinde zincire göre süzgeç yok. Birkaç tane isteyin ve bir Sepolia satırı seçin.

curl -sL 'https://bridge.solidus.network/v1/messages?limit=20' \
  | jq -c '.messages[] | select(.chainKey=="sepolia") | {domain, domainSeq}'

Sonra o mesajın imzalarını isteyin. Rota Bridge API başvurusunda.

curl -sL https://bridge.solidus.network/v1/attestations/11155111/1274

2026-10-08 tarihli yanıttan kısaltılmıştır; bir imzalayan ve bir imza kısaltıldı:

{
  "domain": 11155111,
  "domainSeq": 1274,
  "threshold": 3,
  "signatures": [
    { "signer": "0x99a8...c272", "signature": "0x99bf...1c" }
  ]
}

Gerçek yanıt, imzalayan adresine göre artan sırada dört imza taşır. Şimdi özeti Foundry'nin cast aracıyla, bir kez Sepolia için bir kez Fuji için yeniden kurun ve ilk imzalayanın onu imzalayıp imzalamadığını sorun. 50002 sayısı, üç aynanın da solidusChainId() ile döndürdüğü Solidus test ağı zincir kimliğidir.

A=$(curl -sL https://bridge.solidus.network/v1/attestations/11155111/1274)
ID=$(echo "$A" | jq -r '.messageId[2:]')
HT=$(echo "$A" | jq -r '.solidusHeight')
SIGNER=$(echo "$A" | jq -r '.signatures[0].signer')
SIG=$(echo "$A" | jq -r '.signatures[0].signature')
TAG=$(printf SOLIDUS_BRIDGE_ATTESTATION_V1 | xxd -p -c 100)
for DEST in 11155111 43113; do
  PRE=0x$TAG$(printf '%016x%08x%016x' 50002 $DEST 1274)$ID$(printf '%016x' $HT)
  echo "destination $DEST"
  cast wallet verify --address $SIGNER $(cast keccak $PRE) $SIG
done

Çıktı, adresler kısaltılmış olarak:

destination 11155111
Validation succeeded. Address 0x99a8...c272 signed this message.
destination 43113
Error: Validation failed. Address 0x99a8...c272 did not sign this message.

Aynı baytlar, aynı imza ve aynı imzalayan. Yalnızca hedef değişti.

Okumaya devam edin