首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

区块链应用的密钥管理

编程知识
2024年09月09日 15:31

管理什么密钥?

在区块链应用的基础组件中通常有这样一种功能,需要持续不断的向区块链中发送交易,比如arbitrum的Sequencer需要持续不断的发送L2的区块,stark 需要发送单步证明/rBlock发布 的交易,chainlink需要定时发送datafeed交易。而这每一笔交易都需要L1上的账户做签名,如何安全的使用和管理这个密钥是值得关心的。

结论

就我所看到的一般有两种方式:

  • 通过配置文件配置私钥
  • 使用filekey的方式:
    • 注意file一般需要一个密码,密码是启动后在终端控制台输入

当然,密钥管理不只是简单的将密钥注入到程序里面,而是如何在程序里面安全的使用这些密钥,毕竟如果密钥发在一个可能被外部接口调用的接口中,可能会降低密钥的安全性。

密钥安全等级(依次递减,只考虑加密算法公开的情况):

  • 黑客无法得知任何明文&密文
  • 黑客可以得到密文
  • 黑客可以得到密文对应的明文
  • 黑客可以自行构造明文产生密文

所以在程序中也需要对密钥进行保护。

以太坊中每次使用完私钥会将私钥的地址还原成0地址,就是为了避免私钥在内存中泄漏。

私钥泄漏的原理大致是,geth程序在使用玩内存后会释放内存,而他释放内存并不会把内存值全部置0,而只是告诉操作系统,“这段内存我不用了,你可以分配给别的程序” 。而别的程序申请到这段内存之后,他是可以直接读取这段内存里的值,(经典案例就是 在c语言中如果你初始化一个变量,而不为赋值,那他的值不是0值,而是原先在这个值里面的内存的值)

arbitrum的处理方案

先从最底层的调用开始看

在单步证明的调用中可以看到,这笔交易的用户信息是保存在auth字段中的

func (m *ChallengeManager) IssueOneStepProof(
	ctx context.Context,
	oldState *ChallengeState,
	startSegment int,
) (*types.Transaction, error) {
	position := oldState.Segments[startSegment].Position
	proof, err := m.executionChallengeBackend.GetProofAt(ctx, position)
	if err != nil {
		return nil, fmt.Errorf("error getting OSP from challenge %v backend at step %v: %w", m.challengeIndex, position, err)
	}
	return m.challengeCore.con.OneStepProveExecution(
		m.challengeCore.auth,   // 用户信息保存在这个字段
		m.challengeCore.challengeIndex,
		challengegen.ChallengeLibSegmentSelection{
			OldSegmentsStart:  oldState.Start,
			OldSegmentsLength: new(big.Int).Sub(oldState.End, oldState.Start),
			OldSegments:       oldState.RawSegments,
			ChallengePosition: big.NewInt(int64(startSegment)),
		},
		proof,
	)
}

具体如何使用可以继续点进去看,最终是auth中包含一个变量(函数类型的变量),由这个变量进行签名,(我们需要找到的这个函数的生命周期,也就是密钥的生命周期)。

那么继续往上看,看这个challengeManager的构造方法

func NewChallengeManager(
	ctx context.Context,
	l1client bind.ContractBackend,
	auth *bind.TransactOpts,
	fromAddr common.Address,
	challengeManagerAddr common.Address,
	challengeIndex uint64,
	val *StatelessBlockValidator,
	startL1Block uint64,
	confirmationBlocks int64,
) (*ChallengeManager, error) {
	...
	return &ChallengeManager{
		challengeCore: &challengeCore{
			con:                  con,
			challengeManagerAddr: challengeManagerAddr,
			challengeIndex:       challengeIndex,
			client:               l1client,
			auth:                 auth,   // 也就是上面的auth
			actingAs:             fromAddr,
			startL1Block:         new(big.Int).SetUint64(startL1Block),
			confirmationBlocks:   confirmationBlocks,
		},
		blockChallengeBackend: backend,
		validator:             val,
		wasmModuleRoot:        challengeInfo.WasmModuleRoot,
		maxBatchesRead:        challengeInfo.MaxInboxMessages,
	}, nil
}

可以看到auth是上面传递过来的bind.ContractOpts

继续往上面看,auth来自与Builder这个结构,好在这个结构的构造函数只被调用过一次(我们及假设唯一的构造得到的auth就是我们要找的auth,中间没有发生更改)

func NewBuilder(wallet ValidatorWalletInterface) (*Builder, error) {
	randKey, err := crypto.GenerateKey()
	if err != nil {
		return nil, err
	}
	builderAuth := wallet.AuthIfEoa()
	var isAuthFake bool
	if builderAuth == nil {
		// Make a fake auth so we have txs to give to the smart contract wallet
		builderAuth, err = bind.NewKeyedTransactorWithChainID(randKey, big.NewInt(9999999))
		if err != nil {
			return nil, err
		}
		isAuthFake = true
	}
	return &Builder{
		builderAuth: builderAuth,
		wallet:      wallet,
		L1Interface: wallet.L1Client(),
		isAuthFake:  isAuthFake,
	}, nil
}

builder的auth有两种途径,一种是 AuthIfEoa 也就是从eoa中解析私钥,一种是自己生成私钥

那么关键在与这里的wallet是什么(也就是现在从跟踪auth转移到跟踪wallet)

最终发现wallet在creatNoteImpl方法里面构造的

var wallet staker.ValidatorWalletInterface = validatorwallet.NewNoOp(l1client, deployInfo.Rollup)
if !strings.EqualFold(config.Staker.Strategy, "watchtower") {
	if config.Staker.UseSmartContractWallet || (txOptsValidator == nil && config.Staker.DataPoster.ExternalSigner.URL == "") {// 合约账户
		var existingWalletAddress *common.Address
		if len(config.Staker.ContractWalletAddress) > 0 {
			if !common.IsHexAddress(config.Staker.ContractWalletAddress) {
				log.Error("invalid validator smart contract wallet", "addr", config.Staker.ContractWalletAddress)
				return nil, errors.New("invalid validator smart contract wallet address")
			}
			tmpAddress := common.HexToAddress(config.Staker.ContractWalletAddress)
			existingWalletAddress = &tmpAddress
		}
		wallet, err = validatorwallet.NewContract(dp, existingWalletAddress, deployInfo.ValidatorWalletCreator, deployInfo.Rollup, l1Reader, txOptsValidator, int64(deployInfo.DeployedAt), func(common.Address) {}, getExtraGas)
		if err != nil {
			return nil, err
		}
	} else {
		if len(config.Staker.ContractWalletAddress) > 0 {
			return nil, errors.New("validator contract wallet specified but flag to use a smart contract wallet was not specified")
		}
		wallet, err = validatorwallet.NewEOA(dp, deployInfo.Rollup, l1client, getExtraGas)
		if err != nil {
			return nil, err
		}
	}
}

继续跟踪我们得到wellet中的验证方法是由txOptsValidator提供的

向上继续找txOptsValidator

最重找到mainImpl

	if sequencerNeedsKey || nodeConfig.Node.BatchPoster.ParentChainWallet.OnlyCreateKey {
		l1TransactionOptsBatchPoster, dataSigner, err = util.OpenWallet("l1-batch-poster", &nodeConfig.Node.BatchPoster.ParentChainWallet, new(big.Int).SetUint64(nodeConfig.ParentChain.ID))
		if err != nil {
			flag.Usage()
			log.Crit("error opening Batch poster parent chain wallet", "path", nodeConfig.Node.BatchPoster.ParentChainWallet.Pathname, "account", nodeConfig.Node.BatchPoster.ParentChainWallet.Account, "err", err)
		}
		if nodeConfig.Node.BatchPoster.ParentChainWallet.OnlyCreateKey {
			return 0
		}
	}
	if validatorNeedsKey || nodeConfig.Node.Staker.ParentChainWallet.OnlyCreateKey {
		l1TransactionOptsValidator, _, err = util.OpenWallet("l1-validator", &nodeConfig.Node.Staker.ParentChainWallet, new(big.Int).SetUint64(nodeConfig.ParentChain.ID))
		if err != nil {
			flag.Usage()
			log.Crit("error opening Validator parent chain wallet", "path", nodeConfig.Node.Staker.ParentChainWallet.Pathname, "account", nodeConfig.Node.Staker.ParentChainWallet.Account, "err", err)
		}
		if nodeConfig.Node.Staker.ParentChainWallet.OnlyCreateKey {
			return 0
		}
	}

我们得到l1TransactionOptsValidator是使用nodeConfig.Node.Staker.ParentChainWallet 这个配置项得到的.

最终的数据结构张这个样子

type WalletConfig struct {
	Pathname      string `koanf:"pathname"`
	Password      string `koanf:"password"`
	PrivateKey    string `koanf:"private-key"`
	Account       string `koanf:"account"`
	OnlyCreateKey bool   `koanf:"only-create-key"`
}

继续点到OpenWallet可以看到他是如何处理这些配置项的

在有私钥的情况下最终会走到这个方法

func NewKeyedTransactorWithChainID(key *ecdsa.PrivateKey, chainID *big.Int) (*TransactOpts, error) {
	keyAddr := crypto.PubkeyToAddress(key.PublicKey)
	if chainID == nil {
		return nil, ErrNoChainID
	}
	signer := types.LatestSignerForChainID(chainID)
	return &TransactOpts{
		From: keyAddr,
		Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {  // signer就是我们一直再找的在发送交易时使用到的签名方法
			if address != keyAddr {
				return nil, ErrNotAuthorized
			}
			signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key)
			if err != nil {
				return nil, err
			}
			return tx.WithSignature(signer, signature)
		},
		Context: context.Background(),
	}, nil
}

从这里看出来,私钥始终保存在signer这个方法中,在整个使用过程中没有将私钥作为参数传递的情况。

如果使用的是filekey+密码的情况会进入到这个方法

func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
	log.Warn("WARNING: NewKeyStoreTransactor has been deprecated in favour of NewTransactorWithChainID")
	signer := types.HomesteadSigner{}
	return &TransactOpts{
		From: account.Address,
		Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
			if address != account.Address {
				return nil, ErrNotAuthorized
			}
			signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes())
			if err != nil {
				return nil, err
			}
			return tx.WithSignature(signer, signature)
		},
		Context: context.Background(),
	}, nil
}

程序会根据filekey构造一个keystore,后续签名都是在keystore中签名

注意filekey的密码是在终端控制台输入的,其中的readPass函数如下

func readPass() (string, error) {
	bytePassword, err := term.ReadPassword(syscall.Stdin)
	if err != nil {
		return "", err
	}
	passphrase := string(bytePassword)
	passphrase = strings.TrimSpace(passphrase)
	return passphrase, nil
}
From:https://www.cnblogs.com/blogforeverything/p/18404806
本文地址: http://www.shuzixingkong.net/article/1862
0评论
提交 加载更多评论
其他文章 Mybatis骚操作-通用查询工具类
老项目大多都有对JDBC进行了封装,可以直接执行SQL的工具类,在做项目升级改造的时候(这里仅指整合mybatis),要么全部调整成dao-xml的形式(会有改动代码多的问题,而且看代码时需要xml和java来回切换),要么维持原逻辑不改动(跟mybatis基本无关,同样难以用到mybatis的配置
《数据资产管理核心技术与应用》读书笔记-第四章:数据质量的技术实现(三)
《数据资产管理核心技术与应用》是清华大学出版社出版的一本图书,全书共分10章,第1章主要让读者认识数据资产,了解数据资产相关的基础概念,以及数据资产的发展情况。第2~8章主要介绍大数据时代数据资产管理所涉及的核心技术,内容包括元数据的采集与存储、数据血缘、数据质量、数据监控与告警、数据服务、数据权限
《数据资产管理核心技术与应用》读书笔记-第四章:数据质量的技术实现(三) 《数据资产管理核心技术与应用》读书笔记-第四章:数据质量的技术实现(三) 《数据资产管理核心技术与应用》读书笔记-第四章:数据质量的技术实现(三)
面试官:如何实现线程池任务编排?
任务编排(Task Orchestration)是指管理和控制多个任务的执行流程,确保它们按照预定的顺序正确执行。 1.为什么需要任务编排? 在复杂的业务场景中,任务间通常存在依赖关系,也就是某个任务会依赖另一个任务的执行结果,在这种情况下,我们需要通过任务编排,来确保任务按照正确的顺序进行执行。
面试官:如何实现线程池任务编排? 面试官:如何实现线程池任务编排?
FastGPT 正式接入 Flux,准备好迎接 AI 绘画的狂风了么?
Flux 大家最近都听说了吧?它是一款新推出的 AI 绘画模型,拳打 Stable Diffusion 3,脚踢 Midjourney,整个 AI 绘画界都沸腾了。 Flux 的主创团队来自由 Stable Diffusion 原班人马打造的黑森林实验室 (BlackForestLabs),2024
FastGPT 正式接入 Flux,准备好迎接 AI 绘画的狂风了么? FastGPT 正式接入 Flux,准备好迎接 AI 绘画的狂风了么? FastGPT 正式接入 Flux,准备好迎接 AI 绘画的狂风了么?
计算机网络之TCP/IP协议简介
TCP/IP协议 简介 首先TCP/IP协议不只是表示TCP协议和IP协议两种协议,而是一个协议簇。协议簇是什么并不难理解,就是字面意思,一个由多个协议组合而成的集合体,其中最有代表性的就是TCP和IP这两个协议,除了这两个还有我们熟知的FTP、UDP等协议。当然我们下面主要介绍的还是这两位主角TC
计算机网络之TCP/IP协议简介 计算机网络之TCP/IP协议简介
在stable diffussion中完美修复AI图片
无论您的提示和模型有多好,一次性获得完美图像的情况很少见。修复小缺陷的不可或缺的方法是图像修复(inpainting)
在stable diffussion中完美修复AI图片 在stable diffussion中完美修复AI图片 在stable diffussion中完美修复AI图片
Python存储与读写二进制文件
本文介绍了一种在Python中将Numpy数组转存为一个紧凑的二进制格式的文件,及其使用内存映射的形式进行读取的方案。一个二进制的数据流,不仅可以更加方便页形式的内存映射,相比于传统的Numpy单精度浮点数数组还有一个可哈希的特性。总体来说是一个对于高性能计算十分友好的存储格式,在cudaSPONG
Ollama + JuiceFS:一次拉取,到处运行
今天这篇博客转载自我们的全栈工程师朱唯唯。在使用 Ollma 进行大模型加载时,她尝试使用了 JuiceFS 进行模型共享,JuiceFS 的数据预热和分布式缓存功能显著提升了加载效率,优化了性能瓶颈问题。 01 背景 随着 AI 技术的发展,大模型已经潜移默化地影响着我们的生活。商业 LLM 始终
Ollama + JuiceFS:一次拉取,到处运行