BIP-38(Bitcoin Improvement Proposal 38)是比特币改进提案之一,旨在对私钥进行加密,以方便安全地存储和传输。通过加密私钥,用户可以使用一个密码对其进行保护。解密加密的私钥需要提供正确的密码,从而增加了私钥的安全性。

BIP-38 的主要功能

  1. 私钥加密:将比特币私钥加密成一个人类可读的字符串。
  2. 私钥解密:将加密的私钥解密回原始私钥,需要提供正确的密码。

BIP-38 的应用场景

  1. 纸钱包:BIP-38 加密的私钥可以安全地打印在纸上,即使纸钱包被他人获得,没有密码也无法使用私钥。
  2. 备份和存储:加密的私钥可以安全地存储在不安全的环境中,例如云存储或电子邮件中,而无需担心被窃取。

BIP-38 加密和解密机制

加密私钥的步骤

  1. 生成私钥和公钥
    • 生成一个随机的私钥。
    • 计算对应的公钥。
  2. 计算公钥哈希:计算公钥的哈希值(RIPEMD-160)。
  3. 生成盐值:取公钥哈希的前 4 个字节作为盐值。
  4. 生成加密密钥:使用 Scrypt 密钥派生函数和盐值生成加密密钥。
  5. 加密私钥
    • 将私钥分成两个 16 字节的部分。
    • 分别与加密密钥的前 32 字节和后 32 字节进行异或运算,得到两部分加密的私钥。
  6. 生成加密字符串
    • 拼接加密后的私钥和盐值,并附加校验和,生成最终的加密字符串。

解密私钥的步骤

  1. 解析加密字符串:解码加密字符串,提取盐值和加密的私钥部分。
  2. 生成解密密钥:使用 Scrypt 密钥派生函数和盐值生成解密密钥。
  3. 解密私钥:将加密的私钥部分与解密密钥的前 32 字节和后 32 字节进行异或运算,得到原始的私钥。
  4. 验证解密结果:计算解密后私钥对应的公钥哈希值,并与加密字符串中的哈希值进行比较,验证解密是否正确。

BIP-38 的优点和缺点

优点:

  1. 增强安全性:通过密码保护私钥,使其在不安全的环境中存储时更安全。
  2. 易于备份:加密的私钥可以安全地打印在纸上或存储在云端。

缺点:

  1. 复杂性增加:需要额外的步骤进行加密和解密。
  2. 依赖密码:如果密码丢失或忘记,则无法解密私钥。

实现 BIP-38 的示例代码

以下是一个详细的 Go 语言示例代码,展示了如何使用 BIP-38 加密和解密私钥。

// createHash creates a hash of the passphrase
func createHash(passphrase string) []byte {
	hash := sha256.New()
	hash.Write([]byte(passphrase))
	return hash.Sum(nil)
}

// 双SHA256哈希
func doubleSha256(b []byte) []byte {
	hash := sha256.Sum256(b)
	hash = sha256.Sum256(hash[:])
	return hash[:]
}
// RIPEMD-160哈希
// 已弃用:RIPEMD-160 是旧版哈希,不应用于新应用程序。此外,这个包现在和将来都不会提供优化的实现。
// 所以 使用SHA-256(crypto/sha256)替代
func ripemd160Hash(b []byte) []byte {
	// hasher := ripemd160.New()
	// hasher.Write(b)
	// return hasher.Sum(nil)
	hash := sha256.Sum256(b)
	return hash[:]
}

// BIP38加密
func BIP38Encrypt(wifStr, passphrase string) (string, error) {
	// 尝试解码WIF格式的私钥
	wif, err := btcutil.DecodeWIF(wifStr)
	if err != nil {
		return "", errors.Wrap(err, "无法解码WIF格式私钥")
	}

	// 生成盐值 (来自于公钥的RIPEMD-160前4字节)
	salt := ripemd160Hash(wif.PrivKey.PubKey().SerializeCompressed())[:4]

	// 使用scrypt生成密钥
	scryptKey, err := scrypt.Key([]byte(passphrase), salt, 16384, 8, 8, 64)
	if err != nil {
		return "", fmt.Errorf("scrypt密钥生成失败: %v", err)
	}

	derivedHalf1 := scryptKey[:32]
	derivedHalf2 := scryptKey[32:]

	block, err := aes.NewCipher(derivedHalf2)
	if err != nil {
		return "", fmt.Errorf("AES密码生成失败: %v", err)
	}

	// 私钥的前16字节和后16字节加密
	xorBytes := func(a, b []byte) []byte {
		n := len(a)
		xored := make([]byte, n)
		for i := 0; i < n; i++ {
			xored[i] = a[i] ^ b[i]
		}
		return xored
	}

	privKeyBytes := wif.PrivKey.Serialize()
	encryptedHalf1 := xorBytes(privKeyBytes[:16], derivedHalf1[:16])
	encryptedHalf2 := xorBytes(privKeyBytes[16:], derivedHalf1[16:])

	encryptedBytes := make([]byte, 32)
	block.Encrypt(encryptedBytes[:16], encryptedHalf1)
	block.Encrypt(encryptedBytes[16:], encryptedHalf2)

	// 构建BIP38格式
	bip38Key := append([]byte{0x01, 0x42, 0xC0}, salt...)
	bip38Key = append(bip38Key, encryptedBytes...)

	// 加入校验和
	checksum := doubleSha256(bip38Key)[:4]
	bip38Key = append(bip38Key, checksum...)

	// Base58编码
	return base58.Encode(bip38Key), nil
}

func BIP38Decrypt(encryptedKey, passphrase, network string) (string, error) {
	// Base58解码
	decoded := base58.Decode(encryptedKey)

	// 检查校验和
	checksum := decoded[len(decoded)-4:]
	hash := doubleSha256(decoded[:len(decoded)-4])
	if !reflect.DeepEqual(hash[:4], checksum) {
		return "", errors.New("校验和不匹配")
	}

	// 从加密字节中提取盐值
	salt := decoded[3:7]
	encryptedHalf1 := decoded[7:23]
	encryptedHalf2 := decoded[23:39]

	// 使用scrypt生成密钥
	scryptKey, err := scrypt.Key([]byte(passphrase), salt, 16384, 8, 8, 64)
	if err != nil {
		return "", errors.Wrap(err, "scrypt密钥生成失败")
	}

	derivedHalf1 := scryptKey[:32]
	derivedHalf2 := scryptKey[32:]

	block, err := aes.NewCipher(derivedHalf2)
	if err != nil {
		return "", errors.Wrap(err, "AES密码生成失败")
	}

	decryptedHalf1 := make([]byte, 16)
	block.Decrypt(decryptedHalf1, encryptedHalf1)
	decryptedHalf2 := make([]byte, 16)
	block.Decrypt(decryptedHalf2, encryptedHalf2)

	privKeyBytes := append(decryptedHalf1, decryptedHalf2...)
	for i := 0; i < 32; i++ {
		privKeyBytes[i] ^= derivedHalf1[i]
	}

	// 将解密后的私钥字节切片转换为 *btcec.PrivateKey 类型
	privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes)

	// 使用解密的私钥生成WIF格式
	wif, err := btcutil.NewWIF(privKey, GetNetwork(network), true)
	if err != nil {
		return "", errors.Wrap(err, "生成WIF失败")
	}

	return wif.String(), nil
}

孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。

Author: mengbin

blog: mengbin

Github: mengbin92

cnblogs: 恋水无意

腾讯云开发者社区:孟斯特