Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Panduan mengenai tandatangan Starknet

Panduan mengenai tandatangan Starknet

WBOY
WBOYasal
2024-07-21 11:13:49823semak imbas

A guide on Starknet signatures

Abstrak

Artikel ini menggariskan proses menandatangani dan mengesahkan tandatangan di Starknet. Ia bermula dengan memperkenalkan Abstraksi Akaun dan cara ia mengubah suai pengesahan tandatangan berbanding rantaian blok tradisional seperti Ethereum. Ia kemudian menyediakan contoh kod komprehensif dalam TypeScript dan Go untuk menandatangani mesej dan mengesahkan tandatangan menggunakan dua kaedah yang tersedia di Starknet: menggunakan kunci awam pengguna dan menggunakan alamat akaun pengguna.

Taman permainan tandatangan langsung boleh didapati di https://signatures.felts.xyz

Semua contoh kod yang diberikan dalam artikel ini tersedia dalam repositori GitHub yang berkaitan. Saya ingin mengucapkan terima kasih kepada Thiago atas bantuannya pada coretan kod.

Abstraksi Akaun

Dalam Ethereum, akaun pengguna individu, yang dikenali sebagai Akaun Milik Luaran (EOA), dikawal oleh sepasang kunci peribadi dan awam. Transaksi memerlukan tandatangan daripada kunci persendirian untuk mengubah suai keadaan akaun. Walaupun selamat, sistem ini mempunyai kelemahan yang ketara, seperti kehilangan aset yang tidak dapat dipulihkan jika kunci persendirian hilang atau dicuri, kefungsian dompet terhad dan kekurangan kunci mesra pengguna atau pilihan pemulihan akaun.

Starknet menangani batasan ini melalui Abstraksi Akaun (AA), yang menguruskan akaun melalui kontrak pintar dan bukannya kunci peribadi. Pendekatan ini membolehkan kontrak pintar mengesahkan transaksi mereka, membolehkan ciri seperti yuran gas dilindungi oleh kontrak pintar, berbilang penandatangan untuk satu akaun dan pelbagai tandatangan kriptografi. AA meningkatkan keselamatan dan pengalaman pengguna dengan membolehkan pembangun mereka bentuk model keselamatan tersuai, seperti kunci berbeza untuk transaksi rutin dan bernilai tinggi serta pengesahan biometrik untuk keselamatan yang dipertingkatkan. Ia juga memudahkan pemulihan dan pengurusan utama dengan kaedah seperti pemulihan sosial dan menandatangani transaksi berasaskan perkakasan. Selain itu, AA menyokong putaran kunci, kunci sesi untuk aplikasi web3, dan skema tandatangan dan pengesahan yang pelbagai, membolehkan langkah keselamatan yang disesuaikan. Dengan menangani batasan yang wujud dalam model EOA Ethereum, AA Starknet menyediakan pendekatan pengurusan akaun yang lebih fleksibel, selamat dan mesra pengguna, meningkatkan interaksi blockchain dengan ketara.

Tandatangan

Dengan pemahaman tentang Abstraksi Akaun, kami kini boleh meneroka cara ia mengubah pengesahan tandatangan. Pertama, adalah penting untuk memahami komposisi tandatangan. Lengkung STARK ialah lengkung elips, dan tandatangannya ialah tandatangan ECDSA, yang terdiri daripada dua nilai: r dan s. Tandatangan dijana dengan menandatangani mesej dengan kunci persendirian dan boleh disahkan menggunakan kunci awam. Untuk maklumat lanjut tentang tandatangan ECDSA, rujuk halaman Wikipedia.

Menandatangani Mesej

Dalam Starknet, mesej yang akan ditandatangani biasanya mengikut format EIP-712. Format mesej ini termasuk empat medan wajib: jenis, primaryType, domain dan mesej. Medan jenis memetakan nama jenis kepada definisi jenis yang sepadan. Medan primaryType menentukan jenis utama mesej. Medan domain mengandungi pasangan nilai kunci yang menentukan konteks rantai. Medan mesej termasuk pasangan nilai kunci yang menerangkan mesej. Kami biasanya mewakili mesej sebagai objek JSON:

{
    types: {
        StarkNetDomain: [
            { name: "name", type: "felt" },
            { name: "chainId", type: "felt" },
            { name: "version", type: "felt" },
        ],
        Message: [{ name: "message", type: "felt" }],
    },
    primaryType: "Message",
    domain: {
        name: "MyDapp",
        chainId: "SN_MAIN",
        version: "0.0.1",
    },
    message: {
        message: "hello world!",
    },
}

Untuk menandatangani mesej, anda memerlukan kunci peribadi. Untuk pemahaman yang mendalam tentang proses tandatangan, rujuk algoritma tandatangan ECDSA. Di bawah ialah kod untuk menandatangani mesej.

Skrip Jenis:

import { ec, encode, TypedData, Signer, typedData, WeierstrassSignatureType } from 'starknet';

//--------------------------------------------------------------------------
// Account
//--------------------------------------------------------------------------
const privateKey = '0x1234567890987654321';

const starknetPublicKey = ec.starkCurve.getStarkKey(privateKey);

const fullPublicKey = encode.addHexPrefix(
    encode.buf2hex(ec.starkCurve.getPublicKey(privateKey, false))
);

const pubX = starknetPublicKey
const pubY = encode.addHexPrefix(fullPublicKey.slice(68))

//--------------------------------------------------------------------------
// Message
//--------------------------------------------------------------------------

const messageStructure: TypedData = {
    types: {
        StarkNetDomain: [
            { name: "name", type: "felt" },
            { name: "chainId", type: "felt" },
            { name: "version", type: "felt" },
        ],
        Message: [{ name: "message", type: "felt" }],
    },
    primaryType: "Message",
    domain: {
        name: "MyDapp",
        chainId: "SN_MAIN",
        version: "0.0.1",
    },
    message: {
        message: "hello world!",
    },
};

const messageHash = typedData.getMessageHash(messageStructure, BigInt(starknetPublicKey))

//--------------------------------------------------------------------------
// Signature
//--------------------------------------------------------------------------

const signer = new Signer(privateKey)

let signature: WeierstrassSignatureType;
try {
    signature = (await signer.signMessage(messageStructure, starknetPublicKey)) as WeierstrassSignatureType
} catch (error) {
    console.error("Error signing the message:", error);
}

// signature has properties r and s

Pergi:

package main

import (
    "fmt"
    "math/big"
    "strconv"

    "github.com/NethermindEth/starknet.go/curve"
    "github.com/NethermindEth/starknet.go/typed"
    "github.com/NethermindEth/starknet.go/utils"
)

// NOTE: at the time of writing, starknet.go forces us to create a custom
// message type as well as a method to format the message encoding since
// there is no built-in generic way to encode messages.
type MessageType struct {
    Message string
}

// FmtDefinitionEncoding is a method that formats the encoding of the message
func (m MessageType) FmtDefinitionEncoding(field string) (fmtEnc []*big.Int) {
    if field == "message" {
        if v, err := strconv.Atoi(m.Message); err == nil {
            fmtEnc = append(fmtEnc, big.NewInt(int64(v)))
        } else {
            fmtEnc = append(fmtEnc, utils.UTF8StrToBig(m.Message))
        }
    }
    return fmtEnc
}

func main() {
    //--------------------------------------------------------------------------
    // Account
    //--------------------------------------------------------------------------
    privateKey, _ := new(big.Int).SetString("1234567890987654321", 16)

    pubX, pubY, err := curve.Curve.PrivateToPoint(privateKey)
    if err != nil {
        fmt.Printf("Error: %s\n", err)
        return
    }
    if !curve.Curve.IsOnCurve(pubX, pubY) {
        fmt.Printf("Point is not on curve\n")
        return
    }

    starknetPublicKey := pubX

    // IMPORTANT: this is not a standard way to retrieve the full public key, it
    // is just for demonstration purposes as starknet.go does not provide a way
    // to retrieve the full public key at the time of writing.
    // Rule of thumb: never write your own cryptography code!
    fullPublicKey := new(big.Int).SetBytes(append(append(
        []byte{0x04},                       // 0x04 is the prefix for uncompressed public keys
        pubX.Bytes()...), pubY.Bytes()...), // concatenate x and y coordinates
    )

    //--------------------------------------------------------------------------
    // Message
    //--------------------------------------------------------------------------

    types := map[string]typed.TypeDef{
        "StarkNetDomain": {
            Definitions: []typed.Definition{
                {Name: "name", Type: "felt"},
                {Name: "chainId", Type: "felt"},
                {Name: "version", Type: "felt"},
            },
        },
        "Message": {
            Definitions: []typed.Definition{
                {Name: "message", Type: "felt"},
            },
        },
    }

    primaryType := "Message"

    domain := typed.Domain{
        Name:    "MyDapp",
        ChainId: "SN_MAIN",
        Version: "0.0.1",
    }

    message := MessageType{
        Message: "hello world!",
    }

    td, err := typed.NewTypedData(types, primaryType, domain)
    if err != nil {
        fmt.Println("Error creating TypedData:", err)
        return
    }

    hash, err := td.GetMessageHash(starknetPublicKey, message, curve.Curve)
    if err != nil {
        fmt.Println("Error getting message hash:", err)
        return
    }

    //--------------------------------------------------------------------------
    // Signature
    //--------------------------------------------------------------------------

    r, s, err := curve.Curve.Sign(hash, privateKey)
    if err != nil {
        fmt.Println("Error signing message:", err)
        return
    }
}

Jika anda sedang membangunkan dApp, anda tidak akan mempunyai akses kepada kunci peribadi pengguna. Sebaliknya, anda boleh menggunakan pustaka starknet.js untuk menandatangani mesej. Kod tersebut akan berinteraksi dengan dompet penyemak imbas (biasanya ArgentX atau Braavos) untuk menandatangani mesej. Anda boleh mendapatkan demo langsung di https://signatures.felts.xyz. Berikut ialah kod yang dipermudahkan untuk menandatangani mesej dalam TypeScript menggunakan dompet penyemak imbas (kod penuh tersedia dalam repositori GitHub):

import { connect } from "get-starknet";

const starknet = await connect(); // Connect to the browser wallet

const messageStructure: TypedData = {
    types: {
        StarkNetDomain: [
            { name: "name", type: "felt" },
            { name: "chainId", type: "felt" },
            { name: "version", type: "felt" },
        ],
        Message: [{ name: "message", type: "felt" }],
    },
    primaryType: "Message",
    domain: {
        name: "MyDapp",
        chainId: "SN_MAIN",
        version: "0.0.1",
    },
    message: {
        message: "hello world!",
    },
};

// skipDeploy allows not-deployed accounts to sign messages
const signature = await starknet.account.signMessage(messageStructure, { skipDeploy: true });

Setelah mesej ditandatangani, tandatangan diperoleh dalam bentuk r, s, dan v. Nilai v ialah id pemulihan, yang boleh digunakan untuk memulihkan kunci awam daripada tandatangan (lihat Wikipedia untuk maklumat lanjut ). Walau bagaimanapun, proses pemulihan ini tidak boleh dipercayai sepenuhnya untuk mengesahkan tandatangan melainkan kunci awam penandatangan diketahui terlebih dahulu. Nilai r dan s ialah nilai tandatangan yang digunakan untuk mengesahkan tandatangan.

PENTING: Bergantung pada dompet penyemak imbas, tandatangan mungkin hanya mengembalikan nilai r dan s. Nilai v tidak selalu diberikan.

Verifying a Signature

To verify a signature, the public key is required from a cryptographic perspective. However, due to Account Abstraction in Starknet, access to the public key is not always available. Currently, the public key cannot be retrieved through the browser wallet. Therefore, two methods are distinguished for verifying a signature: using the user's public key (if available) or using the user's address (i.e., account smart contract address).

Using the User's Public Key

If the user's public key is available, the signature can be verified using the public key. Here is the code to verify a signature.

TypeScript:

// following the previous code
const isValid = ec.starkCurve.verify(signature, messageHash, fullPublicKey)

Go:

// following the previous code
isValid := curve.Curve.Verify(hash, r, s, starknetPublicKey, pubY)

Using the User's Address

NOTE: This method works only if the user's account smart contract has been deployed (activated) on the Starknet network. This deployment is typically done through the browser wallet when the user creates an account and requires some gas fees. The skipDeploy parameter is specified in the JavaScript code when signing with the browser wallet. The example code provided earlier will not work with signatures different from the browser wallet since a sample private key was used to sign the message.

IMPORTANT: Avoid using your own private key when experimenting with the code. Always sign transactions with the browser wallet.

If the user's public key is not available, the signature can be verified using the user's account smart contract. By the standard SRC-6, the user account smart contract has a function fn is_valid_signature(hash: felt252, signature: Array) -> felt252; that takes the hash of the message and the signature (in the form of an array of 2 felt252 values: r and s) and returns the string VALID if the signature is valid, or fails otherwise. Here is the code to verify a signature using the user's account address in TypeScript and Go.

TypeScript (simplified for readability):

import { Account, RpcProvider } from "starknet";

const provider = new RpcProvider({ nodeUrl: "https://your-rpc-provider-url" });

// '0x123' is a placeholder for the user's private key since we don't have access to it
const account = new Account(provider, address, '0x123')

try {
    // messageStructure and signature are obtained from the previous code when signing the message with the browser wallet
    const isValid = account.verifyMessage(messageStructure, signature)
    console.log("Signature is valid:", isValid)
} catch (error) {
    console.error("Error verifying the signature:", error);
}

Go (simplified for readability):

import (
    "context"
    "encoding/hex"
    "fmt"
    "math/big"

    "github.com/NethermindEth/juno/core/felt"
    "github.com/NethermindEth/starknet.go/curve"
    "github.com/NethermindEth/starknet.go/rpc"
    "github.com/NethermindEth/starknet.go/utils"
)

...

provider, err := rpc.NewProvider("https://your-rpc-provider-url")
if err != nil {
    // handle error
}

// we import the account address, r, and s values from the frontend (typescript)
accountAddress, _ := new(big.Int).SetString("0xabc123", 16)
r, _ := new(big.Int).SetString("0xabc123", 16)
s, _ := new(big.Int).SetString("0xabc123", 16)

// we need to get the message hash, but, this time, we use the account address instead of the public key. `message` is the same as the in the previous Go code
hash, err := td.GetMessageHash(accountAddress, message, curve.Curve)
if err != nil {
    // handle error
}

callData := []*felt.Felt{
    utils.BigIntToFelt(hash),
    (&felt.Felt{}).SetUint64(2), // size of the array [r, s]
    utils.BigIntToFelt(r),
    utils.BigIntToFelt(s),
}

tx := rpc.FunctionCall{
    ContractAddress: utils.BigIntToFelt(accountAddress),
    EntryPointSelector: utils.GetSelectorFromNameFelt(
        "is_valid_signature",
    ),
    Calldata: callData,
}

result, err := provider.Call(context.Background(), tx, rpc.BlockID{Tag: "latest"})
if err != nil {
    // handle error
}

isValid, err := hex.DecodeString(result[0].Text(16))
if err != nil {
    // handle error
}

fmt.Println("Signature is valid:", string(isValid) == "VALID")

Usage

Signatures can be used in various applications, with user authentication in web3 dApps being a primary use case. To achieve this, use the structure provided above for signature verification using the user's account address. Here is the complete workflow:

  1. The user signs a message with the browser wallet.
  2. Send the user address, message, and signature (r, s) to the backend.
  3. The backend verifies the signature using the user's account smart contract.

Make sure that the message structure is the same on the frontend and backend to ensure the signature is verified correctly.

Conclusion

I hope that this article provided you with a comprehensive understanding of the signatures on Starknet and helped you implement it in your applications. If you have any questions or feedback, feel free to comment or reach out to me on Twitter or GitHub. Thank you for reading!

Sources:

  • https://book.starknet.io/ch04-00-account-abstraction.html
  • https://www.starknetjs.com/docs/guides/signature/
  • https://docs.starknet.io/architecture-and-concepts/accounts/introduction/
  • https://docs.openzeppelin.com/contracts-cairo/0.4.0/accounts#isvalidsignature
  • https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm
  • https://eips.ethereum.org/EIPS/eip-712

Atas ialah kandungan terperinci Panduan mengenai tandatangan Starknet. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn