目录
1.什么是多签账户
2.多签账户的特点
2.1 多个签名者
2.2 最小签名要求
2.3 常见应用场景
3.多签账户实现
3.1 账户的创建
3.1.1 创建新账户
3.1.2 获取创建和初始账户事务
3.1.3 账户的签名
3.2 代币转移操作
Welcome to Code Block's blog
本篇文章主要介绍了
[小试牛刀-Solana多签账户]
❤博主广交技术好友,喜欢文章的可以关注一下❤
1.什么是多签账户
在 Solana 区块链中,多签账户(Multisig Account)是一种智能合约账户,允许多个签名者共同管理和控制账户上的资产或操作。这种机制增强了账户的安全性和灵活性,特别适用于需要多个权限共同批准的操作场景,如资产管理、资金转移、或项目治理。
2.多签账户的特点
2.1 多个签名者
多签账户通常指定多个签名者(即一组公钥),这些签名者可以是个人账户、智能合约账户或其他实体。
每个签名者都有权批准账户的操作,但只有在达到预定义的签名数量时,操作才会生效。
2.2 最小签名要求
多签账户设置了最小签名数(M
)的要求,即在 N
个签名者中,至少需要 M
个签名才能执行账户的任何操作。
这种机制通常被称为 M-of-N
签名方案,例如 2-of-3 签名意味着需要三个人中的两个人签署才能批准操作。
2.3 常见应用场景
项目治理:在去中心化自治组织(DAO)中,使用多签账户来管理资金或项目决策。
资产托管:增加资金管理的安全性,避免单一账户被攻击或失控。
合作项目:需要多个合作方共同控制账户,确保所有操作都有多个利益相关方的同意。
3.多签账户实现
3.1 账户的创建
3.1.1 创建新账户
首先创建一个新的账户,作为要创建多签账户,同时需要其公钥和私钥参与签名,实现代码如下:
function generateSolanaWalletNacl(): { publicKey: string, privateKey: string } { const keyPair = nacl.sign.keyPair(); const publicKeyBase58 = bs58.encode(Buffer.from(keyPair.publicKey)).toString(); const privateKeyBase58 = bs58.encode(Buffer.from(keyPair.secretKey)).toString(); return { publicKey: publicKeyBase58, privateKey: privateKeyBase58 };}
3.1.2 获取创建和初始账户事务
这里multisigPubKey为上面创建的新账户的公钥,payer我们使用另一个账户(已有或创建)的公钥,保证payer中存在可以创建账户的费用,然后获取创建和初始化事务,代码如下:
export async function createMultisigAccount(payer:string,multisigPubKey:PublicKey): Promise<Transaction> { const fromPubkey =new PublicKey(payer); //签名者列表 const signers = [fromPubkey,multisigPubKey]; // 获取多签账户所需的最小余额以免租金 const lamports = await getMinimumBalanceForRentExemptMultisig(connection); // 创建多签账户的指令 const createAccountInstruction = SystemProgram.createAccount({ //这个账户的拥有者 fromPubkey: fromPubkey, //多签账户公钥 newAccountPubkey: multisigPubKey, //免租金最小金额 lamports, space: 355, // 多签账户的空间大小 //属于的程序地址,这里使用SPL程序 programId: TOKEN_PROGRAM_ID, }); // 初始化多签账户的指令 const initializeMultisigInstruction = createInitializeMultisigInstruction( //多签账户 multisigPubKey, //签名者列表 signers, //最小签名数量,这里是2 requiredSigners, //属于的程序地址,这里使用SPL程序 TOKEN_PROGRAM_ID ); //创建transaction并添加创建和初始化指令 const transaction = new Transaction() .add(createAccountInstruction,initializeMultisigInstruction); //最近的区块hash transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; //付费者为拥有者 transaction.feePayer=fromPubkey; console.log("签名者:"+signers) console.log("multisigPubKey:"+multisigPubKey) return transaction;}
3.1.3 账户的签名
账户创建时需要签名者列表中的用户分别对transaction进行签名,(是对同一transaction进行签名),如果是拥有者公私钥在本地,可以将公私钥生成Keypair,可使用以下方式签名:
const signer1=Keypair.generate()const signer2=Keypair.generate()//签名者1和签名者2 的keypairtransaction.sign(signer1,signer2)
但若是外部签名,如walletconnect连接的钱包用户作为签名者,则需要使用以下方式进行签名(重点):
首先,获取本地公私钥的签名,注意这里并没有对transaction进行签名,只是获取签名:
export async function getLocalSign(transaction:Transaction,signer:Keypair){ const signature = nacl.sign.detached(transaction.serializeMessage(), signer.secretKey); return signature;}
然后,walletconnet用户连接钱包对创建和初始化账户事务签名后,会获取到一个signature,将这个signature和本地公私钥获取的signature添加到transaction中,代码如下:
//钱包签名 const result =await sendWcTransaction(signClient,session,transaction); //本地签名 const signature = nacl.sign.detached(transaction.serializeMessage(), bs58.decode(privateKey)); transaction.addSignature( transaction.feePayer, //这里返回的是字符串,所以需要转换 Buffer.from(bs58.decode(result.signature)) ) //<>! transaction.addSignature( multisigPubKey, //本地获取的是uint8array,无需转换 Buffer.from(signature) )
为保证签名的有效性,需要先验证签名,验证方式如下:
//验证签名 const valid = transaction.verifySignatures(); if (!valid){ //验证未通过则不进行操作,或打印 return ; }
验证通过后,就可以通过以下代码将要进行的创建事务提交到链上:
export async function sendTransaction(transaction:Transaction){ try{ const txId =await connection.sendRawTransaction(transaction.serialize()) return txId; }catch(error){ console.error('Exception occurred:', error); return 'error'; }}
链上截图:
可以看到这里创建并初始化了一个Multisig账户,即多签账户,并且Signers有两个地址,这样就完成了多签账户的创建.
3.2 代币转移操作
多签账户操作的转移和创建类似,同样需要两个或多个账户对transaction进行签名,获取transaction返回的[transaction,senderKeypair]分别是事务和本地的keyPair,方便后续签名使用,代码如下:
export async function getMultisigTransferTransaction( ownerPublicKey:string, senderPublicKey: string, privateKey: string, drawPublicKey: string, tokenAmount: number, drawData: string): Promise<[Transaction,Keypair]> { const ownerPubKey = new PublicKey(ownerPublicKey); const senderPubkey = new PublicKey(senderPublicKey); const drawPubkey = new PublicKey(drawPublicKey); const tokenMintAddress = BOGGY_TOKEN_MINT; const senderKeypair = Keypair.fromSecretKey(Uint8Array.from(bs58.decode(privateKey))); try { const sourceTokenAccount = await getAssociatedTokenAddress(tokenMintAddress,senderPubkey); const destTokenAccount = await getAssociatedTokenAddress(tokenMintAddress,drawPubkey); const transferInstruction = createTransferInstruction( sourceTokenAccount, destTokenAccount, senderPubkey, tokenAmount * 1e9, [senderPubkey,ownerPubKey], TOKEN_PROGRAM_ID ); // 构造 Memo 指令 const memoInstruction = new TransactionInstruction({ keys: [ { pubkey: ownerPubKey, isSigner: true , isWritable:false }, { pubkey: senderPubkey, isSigner: true, isWritable: false } ], programId: MEMO_PROGRAM_ID, data: Buffer.from(drawData,'utf-8') }); // 创建 compute unit price 指令,提高交易速度 const computeUnitPriceInstruction = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 7500, }); const computeUnitLimitInstruction=ComputeBudgetProgram.setComputeUnitLimit({ units:200000 }) const transaction = new Transaction().add(computeUnitPriceInstruction,computeUnitLimitInstruction,transferInstruction,memoInstruction); transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; transaction.feePayer = ownerPubKey; return [transaction,senderKeypair]; } catch (error) { console.error('Exception occurred:', error); }}
签名和发送的步骤和创建时签名是一样的,这里就不重复写了.
对区块链内容感兴趣可以查看我的专栏:小试牛刀-区块链
感谢您的关注和收藏!!!!!!