详解多签钱包技术和使用

Desig Labs的多签技术,为创建任意数量密钥才能打开的区块链钱包提供可能。

详解多签钱包技术和使用

Sui上的多签钱包需要两个或以上密钥才能打开,这为共享资产控制提供了独特的方式。在传统使用案例中,可以回溯到安全保险箱的使用,其中客户和银行各自持有一个实体密钥,保险箱需要两个密钥一起才能打开。

Desig Labs开发了一个了多签软件开发工具包(SDK),提供给希望整合这一技术的开发者使用,同时还开发了一款多链多签钱包,用于管理个人和组织机构的共享账户。

虽然这与保险箱类似,但多签钱包提供了更广泛的使用场景。用户可以配置它需要的任意数量的密钥或签名来打开钱包。更实用的是,这个多签钱包可以设置更多数量的授权签名,该数量可以大于打开钱包所需的密钥数量。例如,一个由20人组成的委员会,每个人都有一把授权的密钥,但只需要10个密钥就能访问钱包中的数字资产。

在Sui上,多签钱包可以持有各种类型的数字资产。以第三方托管为例,一方可能同意从另一方购买实物商品,买方可以将付款放入一个多签钱包,双方同时都拥有密钥。一旦买方收到商品,他们可以使用自己的密钥解锁钱包,然后卖方就可以解锁钱包并提取资金。

这个例子可以防止欺诈行为的发生,若卖方没有发货,他们就无法在买方使用自己的密钥之前提取资金。如果双方对购买存在争议,资金将无法访问,直到争议解决为止。

下图显示了三把钥匙,其中两把突出显示用于开锁
多签钱包需要两个及以上的密钥才能打开,并且可能存在比解锁钱包所需数量更多的密钥

除了共享账户的用例,多签钱包还提供了更高的透明度。所有的交易策略、签署者和实际交易都可以在区块链上公开查看,这就提供了完整的可见性和问责制。这种透明度使得跟踪和审计交易变得更加容易,这对于必须遵守监管要求的组织和机构来说非常重要。

多签钱包的进步

虽然传统的多签钱包已经发展一段时间了,但为了满足人们日益增长的对于高级安全功能的需求,研究人员开发出了更新且更复杂的解决方案。如上所述,最早的数字多签钱包需要多个密钥来授权交易,还可以进行许多不同的配置。

账户抽象

新的ERC-4337标准允许智能合约在多签钱包中持有资金,而无需智能合约参与交易。以太坊的贡献者开发了这项技术,以减少创建传统数字钱包的障碍,并实现现代银行apps常见的交易类型。用户不需要助记词,即可设置自动和重复付款。

这项技术提供了更大的灵活性以及对交易的控制,因此对于那些希望在享受智能合约好处的同时保持高安全性的人来说,它是一个很不错的选择。

阈值签名方案和多方计算

Desig Labs的多签钱包,使用了一组称为阈值签名方案和多方计算(TSS-MPC)的技术。尽管这些技术并不新鲜,但它们为多签钱包提供了一个极好的应用案例。

TSS并不创建多个加密密钥,而是将一个私钥分割成多个部分并分发给多个参与方。当每个部分被合并提交时,它们会生成一个主密钥。在链上,这个密钥的功能类似于单一密钥钱包的签名。

MPC作为一种安全方法用于存储密钥份额,与TSS相辅相成。MPC是一个节点网络,用于计算一个函数而不泄露每个参与方的私密数据。这种组合确保没有单一参与方可以访问完整的私钥,从而增加了攻击者破坏系统的难度。

在Sui上应用TSS

Sui支持 k 个中的 n 个多签交易,其中 k 是阈值,n是所有参与方的总权重。参与方的最大数量需要 <= 10.

工作流程示例

以下示例将演示在Sui的命令行界面中生成用于多签交易的密钥。

第1步:将密钥添加到Sui密钥库

以下命令将为每个支持的密钥方案生成一个Sui地址和密钥,并将其添加到 sui.keystore中,然后列出这些密钥。

$SUI_BINARY client new-address ed25519
$SUI_BINARY client new-address secp256k1
$SUI_BINARY client new-address secp256r1

$SUI_BINARY keytool list            

响应的格式类似以下内容,但显示的是实际的地址和密钥:

Sui Address | Public Key (Base64) | Scheme
-----------------------------------------------------------------------
$ADDR_1     | $PK_1               | secp256r1
$ADDR_2     | $PK_2               | secp256k1
$ADDR_3     | $PK_3               | ed25519        

第2步:创建多签地址

输入公钥列表及其相应的权重,如下所示,将创建一个多签地址。

$SUI_BINARY keytool multi-sig-address --pks $PK_1 $PK_2 $PK_3 --weights 1 2 3 --threshold 3

MultiSig address: $MULTISIG_ADDR         

响应的格式类似以下内容:

Participating parties:
Sui Address | Public Key (Base64)| Weight
------------------------------------------
$ADDR_1    | $PK_1              |   1
$ADDR_2    | $PK_2              |   2
$ADDR_3    | $PK_3              |   3            

第3步:向多签地址发送对象

以下代码片段使用默认URL从本地网络请求gas,按照Sui文档中的指导进行操作。

curl --location --request POST '<http://127.0.0.1:9123/gas>' --header 'Content-Type: application/json' --data-raw "{ \\"FixedAmountRequest\\": { \\"recipient\\": \\"$MULTISIG_ADDR\\" } }"

响应的格式类似以下内容:

{"transferred_gas_objects":[{"amount":200000,"id":"$OBJECT_ID", ...}]}

第4步:序列化交易

这一步展示了如何使用属于多签地址的对象,并对转账进行序列化以进行签名。注意, $TX_BYTES 可以是任何序列化的交易数据,其中发送方是多签地址。只需使用 --serialize-output 参数输出Base64编码的交易字节。

$SUI_BINARY client transfer --to $MULTISIG_ADDR --object-id $OBJECT_ID --gas-budget 1000 --serialize-output

Raw tx_bytes to execute: $TX_BYTES

第5步:使用两个密钥对交易进行签名

以下代码示例,使用 sui.keystore中的两个密钥对交易进行签名。只要交易序列化并使用函数就可以使用其他工具对交易进行签名: flag || sig || pk.

$SUI_BINARY keytool sign --address $ADDR_1 --data $TX_BYTES

Raw tx_bytes to execute: $TX_BYTES
Serialized signature (`flag || sig || pk` in Base64): $SIG_1

$SUI_BINARY keytool sign --address $ADDR_2 --data $TX_BYTES

Raw tx_bytes to execute: $TX_BYTES
Serialized signature (`flag || sig || pk` in Base64): $SIG_2

第6步:将单个签名组合成多签地址

以下示例演示了如何将两个签名组合在一起。

$SUI_BINARY keytool multi-sig-combine-partial-sig --pks $PK_1 $PK_2 $PK_3 --weights 1 2 3 --threshold 3 --sigs $SIG_1 $SIG_2

MultiSig address: $MULTISIG_ADDRESS # Informational
MultiSig parsed: $HUMAN_READABLE_STRUCT # Informational
MultiSig serialized: $SERIALIZED_MULTISIG            

第7步:使用多签地址执行交易

以下示例使用多签地址执行交易。

$SUI_BINARY client execute-signed-tx --tx-bytes $TX_BYTES --signatures $SERIALIZED_MULTISIG

Sui上基于MPC的多签

MPC方法存储了上面所述生成的各种密钥份额。在MPC实现中,Desig Labs依赖一种称为Shamir’s Secret Sharing(SSS)的算法,它可以在一群人中共享一个秘密,但有一个限制,在群组中只有达到一定阈值数量的人将他们掌握的秘密片段结合时,才能恢复整个秘密。

SSS允许从特定数量的秘密片段中数学推导出整个秘密,在Desig Labs的示例中,这个秘密就是一个密钥。例如,一个被分成20个片段的密钥可能需要至少10个片段才能推导出整个密钥。即使黑客成功窃取了其中的五个片段,他们也无法推导出整个密钥。

这种能力在分布式系统(例如区块链)中非常有效,建立在共享资源的原则上,同时由所处环境强制实施,保证数据的完整性。

将SSS构建到TSS-MPC中带来了许多优势。首先,密钥不可能被黑客盗取或丢失。由于密钥的各个部分被分散在多个人之间,要获得整个密钥,黑客就需要入侵大量的人。同样,如果这个群组中的一些人丢失了他们的密钥部分,只要剩余的部分足够多,仍然可以推导出整个密钥。从社区的角度来看,这项技术要求获得多数同意才能使用密钥,防止任何一个人或少于阈值数的人获得完整密钥。

TSS-MPC的复杂性意味着实施起来需要一些专业知识,并且用比较简单的方案使用更多的计算资源。当与网络问题结合在一起时,这一点可能导致用户在初始生成密钥时出现延迟。在大规模群组中协调共享密钥也是一个问题,因为这通常需要良好、清晰的沟通。

在Sui上构建TSS-MPC多签

通过Desig Labs的TSS-MPC实现和下面的代码示例,希望使构建者能够轻松将多签技术添加到他们的项目中。开始前,您需要安装 @desig/web3 use Yarn package manager。在命令行界面中,输入 yarn add @desig/web3.

示例 1:

下面的代码示例显示了如何快速创建一个多签、解锁操作的提案,以及如果有足够的密钥持有者同意的话,将会解锁的事务结果。

import { encode } from 'bs58'
import { utils } from '@noble/ed25519'
import { DesigKeypair, Multisig, Signer, Proposal, Transaction } from '@desig/web3'

const cluster = '<https://mainnet.desig.io>'
const privkey = encode(utils.randomPrivateKey())
const secretShare = 'ed25519/<master>/<share>'
const keypair = DesigKeypair.fromSecret(secret)

// Create multisig instance
const dMultisig = new Multisig(cluster, privkey)
await dMultisig.getMultisigs()
await dMultisig.getMultisig('multisigId')

// Create signer instance
const dSigner = new Signer(cluster, privkey)
await dSigner.getAllSigners()
await dSigner.getSigner('signerId')

// Create proposal instance
const dProposal = new Proposal(cluster, privkey, keypair)
await dProposal.approveProposal('proposalId')
await dProposal.initializeProposal({ raw: ..., msg: ..., chainId: ... })

// Create transaction instance
const dTransaction = new Transaction(cluster, privkey, keypair)
await dTransaction.initializeTransaction({ type: ..., params: { ... }})

await dTransaction.getTransaction('transactionId')
await dTransaction.signTransaction('transactionId')

示例2:

下面的示例代码描述了使用多签来批准Sui转账的完整流程。

第1步:创建多签

import { encode } from 'bs58'
import { utils } from '@noble/ed25519'
import { Multisig } from '@desig/web3'
import { EdCurve } from '@desig/core'
import { Curve } from '@desig/supported-chains'

const privkey = encode(utils.randomPrivateKey())
const pubkey = encode(EdCurve.getPublicKey(decode(privkey)))

const dMultisig = new Multisig('<https://mainnet.desig.io>', privkey)
const t = 2 // threshold
const n = 2 // total weights
const pubkeys = [pubkey, pubkey] // You can change it to other members' pubkey
const curve = Curve.ed25519 // Refer <https://chainlist.desig.io/> to find the corresponding chain
const multisig = await dMultisig.initializeMultisig(curve, {
	t,
	n,
	pubkeys,
})

第2步:为Sui创建转账提案

import { decode } from 'bs58'
import { DesigKeypair, Proposal } from '@desig/web3'
import { toSuiAddress, SuiDevnet } from '@desig/supported-chains'
import { blake2b } from '@noble/hashes/blake2b'
import {
	Connection,
	JsonRpcProvider,
	messageWithIntent,
	IntentScope,
	Ed25519PublicKey,
	toSerializedSignature,
} from '@mysten/sui.js'
import { transfer, sendAndConfirm } from '<appendix_transfer_sui'

const connection = new Connection({ fullnode: '<sui_fullnode>' })
const provider = new JsonRpcProvider(connection)

// Create alice keypair and bob keypair from secrets sent to the emails
const aliceKeypair = new DesigKeypair('<alice_secret_share>')
const bobKeypair = new DesigKeypair('<bob_secret_share>')

// aliceKeypair.masterkey === bobKeypair.masterkey is true
const masterkey = toSuiAddress(aliceKeypair.masterkey)

/**
	* Alice initializes a transaction
	*/
const aliceProposal = new Proposal(
	'<https://mainnet.desig.io>',
	alicePrivkey,
	aliceKeypair,
)
const bobProposal = new Proposal(
	'<https://mainnet.desig.io>',
	bobPrivkey,
	bobKeypair,
)

const tx = await transfer(masterkey, 5000)
const txSerialize = await tx.build({ provider })
const msg = messageWithIntent(IntentScope.TransactionData, txSerialize)
const digest = blake2b(msg, { dkLen: 32 })

const { id: proposalId } = await 
aliceProposal.initializeProposal({
	raw: txSerialize,
	msg: digest,
	chainId: new SuiDevnet().chainId,
})

/**
	* Alice approves the transaction
	*/
await aliceProposal.approveProposal(proposalId)
/**
	* Bob approves the transaction
	*/
await bobProposal.approveProposal(proposalId)

/**
	* Bob finalizes the transaction
*/
const { sig } = await bobProposal.finalizeSignature(proposalId)
const { raw } = await bobProposal.getProposal(proposalId)

const rawTx = decode(raw)
const serializedSig = toSerializedSignature({
	pubKey: new Ed25519PublicKey(masterkey),
	signature: sig,
	signatureScheme: 'ED25519',
})
/**
	* Bob submits the transaction
*/
const txHash = await sendAndConfirm(serializedSig, rawTx)

第3步:进行Sui转账操作

import { TransactionBlock, Connection, JsonRpcProvider } from '@mysten/sui.js'

const connection = new Connection({ fullnode: '<sui_fullnode>' })
const provider = new JsonRpcProvider(connection)

// Init transaction transfer
export const transfer = async (payer: string, amount: number) => 
{
	const tx = new TransactionBlock()
	const [coin] = tx.splitCoins(tx.gas, [tx.pure(amount.toString())])
	tx.transferObjects([coin], tx.pure(payer))
	tx.setSender(payer)
	return tx
}

export const sendAndConfirm = async (
	signature: string,
	txBlock: Uint8Array,
) => {
	const { digest } = await provider.executeTransactionBlock({
		signature,
		transactionBlock: txBlock,
	})
	return digest
}

共享安全

Desig Labs开发的多签技术为共享资产提供了高安全性的重要解决方案,无论是临时性的资产类型,比如托管,还是永久性的资产类型,如集体财政。Sui的去中心化使其成为与多签钱包提供的共享控制相辅相成的环境。

请阅读Desig Labs的多签文档,并使用上面的代码示例将其集成到您自己的项目中。如果您有任何问题,请与Desig Labs联系

期待看到更多项目为多签方案找到更多安全的用途。