签名和发送交易
Sui 中的交易代表对特定功能的调用(比如调用智能合约函数),该调用会对输入进行执行,从而定义交易的结果。
输入可以是对象引用(可能是一个拥有对象、一个不可变对象或共享对象的引用),也可以是编码值(例如,用作 Move 调用参数的字节向量)。构建交易通常通过使用可编程交易块(PTB)完成,用户对交易进行签名,然后提交执行。
签名是使用钱包拥有的私钥提供的,其公钥必须与交易发送方的 Sui 地址一致。
Sui 使用 SuiKeyPair
生成签名,该签名与意图消息(intent || tx_data 的 bcs 字节的 Blake2b 哈希摘要
)的 Blake2b 哈希摘要相对应。目前支持的签名方案有 Ed25519 Pure
、ECDSA Secp256k1
、ECDSA Secp256r1
、Multisig
和 zkLogin
。
可以使用 SuiKeyPair
实例化 Ed25519 Pure
、ECDSA Secp256k1
和 ECDSA Secp256r1
并用其对交易进行签名。请注意,本指南不适用于 Multisig
和 zkLogin
,有关说明,请参阅它们各自的页面(Multisig 和 zkLogin)。
有了签名和交易字节,就可以提交交易以执行。
工作流程
以下高级过程描述了构建、签名和执行链上交易的整体工作流程:
- 通过创建
TransactionBlock
来构建交易数据,其中包含多个交易链接。有关更多信息,请参见构建可编程交易块。 - SDK 的内置燃气估算和币选择选择燃气币。
- 对交易进行签名以生成签名。
- 提交
TransactionBlock
及其签名以进行链上执行。
如果要使用特定的燃气币,请首先找到要用于支付燃气的燃气币对象 ID,并在 PTB 中明确使用它。如果没有燃气币对象,请使用 splitCoin 交易创建一个燃气币对象。拆分燃气币交易应该是 PTB 中的第一个交易调用。
示例
以下示例演示如何使用 Rust、TypeScript 或 Sui CLI 签署和执行交易。
- TypeScript
- Rust
- Sui CLI
使用 Sui TypeScript SDK 实例化密钥对以及通过 Sui TypeScript SDK 推导其公钥和 Sui 地址有多种方法。
import { SuiClient, getFullnodeUrl } from '@mysten/sui.js/client';
import { TransactionBlock } from '@mysten/sui.js/transactions';
import { type Keypair } from '@mysten/sui.js/cryptography';
import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519';
import { Secp256k1Keypair } from '@mysten/sui.js/keypairs/secp256k1';
import { Secp256r1Keypair } from '@mysten/sui.js/keypairs/secp256r1';
import { fromHEX } from '@mysten/bcs';
const kp_rand_0 = new Ed25519Keypair();
const kp_rand_1 = new Secp256k1Keypair();
const kp_rand_2 = new Secp256r1Keypair();
const kp_import_0 = Ed25519Keypair.fromSecretKey(fromHex("0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82"));
const kp_import_1 = Secp256k1Keypair.fromSecretKey(fromHex("0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82"));
const kp_import_2 = Secp256r1Keypair.fromSecretKey(fromHex("0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82"));
// $MNEMONICS refers to 12/15/18/21/24 words from the wordlist, e.g. "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire". Refer to [Keys and Addresses](/concepts/cryptography/transaction-auth/keys-addresses.mdx) for more.
const kp_derive_0 = Ed25519Keypair.deriveKeypair("$MNEMONICS");
const kp_derive_1 = Secp256k1Keypair.deriveKeypair("$MNEMONICS");
const kp_derive_2 = Secp256r1Keypair.deriveKeypair("$MNEMONICS");
const kp_derive_with_path_0 = Ed25519Keypair.deriveKeypair("$MNEMONICS", "m/44'/784'/1'/0'/0'");
const kp_derive_with_path_1 = Secp256k1Keypair.deriveKeypair("$MNEMONICS", "m/54'/784'/1'/0/0");
const kp_derive_with_path_2 = Secp256r1Keypair.deriveKeypair("$MNEMONICS", "m/74'/784'/1'/0/0");
// replace `kp_rand_0` with the variable names above.
const pk = kp_rand_0.getPublicKey();
const sender = pk.toSuiAddress();
// create an example transaction block.
const txb = new TransactionBlock();
txb.setSender(sender);
txb.setGasPrice(5);
txb.setGasBudget(100);
const bytes = await txb.build();
const serializedSignature = (await keypair.signTransactionBlock(bytes)).signature;
// verify the signature locally
expect(await keypair.getPublicKey().verifyTransactionBlock(bytes, serializedSignature)).toEqual(true);
// define sui client for the desired network.
const client = new SuiClient({ url: getFullnodeUrl('testnet') });
// execute transaction.
let res = client.executeTransactionBlock({
transactionBlock: bytes,
signature: serializedSignature,
});
console.log(res);
下面的完整代码示例可以在 crates/sui-sdk 中找到。
使用 Sui Rust SDK 实例化 SuiKeyPair
并通过 Sui Rust SDK 推导其公钥和 Sui 地址有多种方法。
// deterministically generate a keypair, testing only, do not use for mainnet, use the next section to randomly generate a keypair instead.
let skp_determ_0 =
SuiKeyPair::Ed25519(Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])));
let _skp_determ_1 =
SuiKeyPair::Secp256k1(Secp256k1KeyPair::generate(&mut StdRng::from_seed([0; 32])));
let _skp_determ_2 =
SuiKeyPair::Secp256r1(Secp256r1KeyPair::generate(&mut StdRng::from_seed([0; 32])));
// randomly generate a keypair.
let _skp_rand_0 = SuiKeyPair::Ed25519(get_key_pair_from_rng(&mut rand::rngs::OsRng).1);
let _skp_rand_1 = SuiKeyPair::Secp256k1(get_key_pair_from_rng(&mut rand::rngs::OsRng).1);
let _skp_rand_2 = SuiKeyPair::Secp256r1(get_key_pair_from_rng(&mut rand::rngs::OsRng).1);
// import a keypair from a base64 encoded 32-byte `private key`.
let _skp_import_no_flag_0 = SuiKeyPair::Ed25519(Ed25519KeyPair::from_bytes(
&Base64::decode("1GPhHHkVlF6GrCty2IuBkM+tj/e0jn64ksJ1pc8KPoI=")
.map_err(|_| anyhow!("Invalid base64"))?,
)?);
let _skp_import_no_flag_1 = SuiKeyPair::Ed25519(Ed25519KeyPair::from_bytes(
&Base64::decode("1GPhHHkVlF6GrCty2IuBkM+tj/e0jn64ksJ1pc8KPoI=")
.map_err(|_| anyhow!("Invalid base64"))?,
)?);
let _skp_import_no_flag_2 = SuiKeyPair::Ed25519(Ed25519KeyPair::from_bytes(
&Base64::decode("1GPhHHkVlF6GrCty2IuBkM+tj/e0jn64ksJ1pc8KPoI=")
.map_err(|_| anyhow!("Invalid base64"))?,
)?);
// import a keypair from a base64 encoded 33-byte `flag || private key`. The signature scheme is determined by the flag.
let _skp_import_with_flag_0 =
SuiKeyPair::decode_base64("ANRj4Rx5FZRehqwrctiLgZDPrY/3tI5+uJLCdaXPCj6C")
.map_err(|_| anyhow!("Invalid base64"))?;
let _skp_import_with_flag_1 =
SuiKeyPair::decode_base64("AdRj4Rx5FZRehqwrctiLgZDPrY/3tI5+uJLCdaXPCj6C")
.map_err(|_| anyhow!("Invalid base64"))?;
let _skp_import_with_flag_2 =
SuiKeyPair::decode_base64("AtRj4Rx5FZRehqwrctiLgZDPrY/3tI5+uJLCdaXPCj6C")
.map_err(|_| anyhow!("Invalid base64"))?;
// replace `skp_determ_0` with the variable names above
let pk = skp_determ_0.public();
let sender = SuiAddress::from(&pk);
接下来,使用带有默认燃气币、燃气预算和燃气价格的示例可编程交易块构建的交易数据进行签名。有关更多信息,请参阅 构建可编程交易块。
// construct an example programmable transaction.
let pt = {
let mut builder = ProgrammableTransactionBuilder::new();
builder.pay_sui(vec![sender], vec![1])?;
builder.finish()
};
let gas_budget = 5_000_000;
let gas_price = sui_client.read_api().get_reference_gas_price().await?;
// create the transaction data that will be sent to the network.
let tx_data = TransactionData::new_programmable(
sender,
vec![gas_coin.object_ref()],
pt,
gas_budget,
gas_price,
);
将签名提交到意图消息的 Blake2b 散列摘要 (intent || tx_data 的 bcs 字节
)。
// derive the digest that the keypair should sign on, i.e. the blake2b hash of `intent || tx_data`.
let intent_msg = IntentMessage::new(Intent::sui_transaction(), tx_data);
let raw_tx = bcs::to_bytes(&intent_msg).expect("bcs should not fail");
let mut hasher = sui_types::crypto::DefaultHash::default();
hasher.update(raw_tx.clone());
let digest = hasher.finalize().digest;
// use SuiKeyPair to sign the digest.
let sui_sig = skp_determ_0.sign(&digest);
// if you would like to verify the signature locally before submission, use this function. if it fails to verify locally, the transaction will fail to execute in Sui.
let res = sui_sig.verify_secure(
&intent_msg,
sender,
sui_types::crypto::SignatureScheme::ED25519,
);
assert!(res.is_ok());
最后,提交带有签名的交易。
let transaction_response = sui_client
.quorum_driver_api()
.execute_transaction_block(
sui_types::transaction::Transaction::from_generic_sig_data(
intent_msg.value,
Intent::sui_transaction(),
vec![GenericSignature::Signature(sui_sig)],
),
SuiTransactionBlockResponseOptions::default(),
None,
)
.await?;
当首次使用Sui CLI时,它会在你的机器上的 ~/.sui/keystore
目录下创建一个本地文件,其中包含私钥列表(编码为 Base64 编码的 flag || 32-byte-private-key
)。你可以通过指定其地址使用任何密钥来签署交易。使用 sui keytool list
查看地址列表。
有三种初始化密钥的方法:
# generate randomly.
sui client new-address ed25519
sui client new-address secp256k1
sui client new-address secp256r1
# import the 32-byte private key to keystore.
sui keytool import "0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82" ed25519
sui keytool import "0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82" secp256k1
sui keytool import "0xd463e11c7915945e86ac2b72d88b8190cfad8ff7b48e7eb892c275a5cf0a3e82" secp256r1
# import the mnemonics (recovery phrase) with derivation path to keystore.
# $MNEMONICS refers to 12/15/18/21/24 words from the wordlist, e.g. "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire". Refer to [Keys and Addresses](/concepts/cryptography/transaction-auth/keys-addresses.mdx) for more.
sui keytool import "$MNEMONICS" ed25519
sui keytool import "$MNEMONICS" secp256k1
sui keytool import "$MNEMONICS" secp256r1
在 CLI 中创建转账交易。将 $SUI_ADDRESS
设置为用于签署的密钥对应的地址。$GAS_COIN_ID
是发送方拥有的用于支付 gas 的对象 ID。$GAS_BUDGET
是用于执行交易的预算。然后使用与发送方地址对应的私钥进行签名。$MNEMONICS
是从助记词表中选择的 12/15/18/21/24 个单词,例如 "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire"。有关更多信息,请参阅密钥和地址。
sui client gas
sui client transfer-sui --to $SUI_ADDRESS --sui-coin-object-id $GAS_COIN_ID --gas-budget $GAS_BUDGET --serialize-unsigned-transaction
sui keytool sign --address $SUI_ADDRESS --data $TX_BYTES
sui client execute-signed-tx --tx-bytes $TX_BYTES --signatures $SERIALIZED_SIGNATURE