门罗币如何实现离线签

  • Leo
  • 更新于 2024-10-25 14:49
  • 阅读 553

简介门罗币(Monero,缩写:XMR)是一个创建于2014年4月开源加密货币,它着重于隐私、分权和可扩展性。与自比特币衍生的许多加密货币不同,Monero基于CryptoNote协议,并在区块链模糊化方面有显著的算法差异。第一步先安装门罗钱包并创建个人账户https://www.getmo

简介

门罗币(Monero,缩写:XMR)是一个创建于2014年4月开源加密货币,它着重于隐私、分权和可扩展性。与自比特币衍生的许多加密货币不同,Monero基于CryptoNote协议,并在区块链模糊化方面有显著的算法差异。

第一步 先安装门罗钱包并创建个人账户

https://www.getmonero.org/zh-cn/downloads/#gui image.png

image.png

按照指引创建自己的助记词,个人秘钥对

第二步 进行钱包调研

第一步助记词

Monero 不使用 BIP-39 标准来生成助记词。相反,Monero 使用自己的助记词系统,其生成助记词的算法与 BIP-39 不同。Monero 助记词通常基于一个随机的 256-bit 的种子,使用了专门的词汇表生成的助记词,并且其格式与 BIP-39 助记词不兼容。 助记词长度为 25个词语 和 13个词语

export function createMeneroMnemonic(len:any){
    const buffer = crypto.randomBytes(32);
    const hexStr = buffer.toString('hex');
    const out = [];

    for(let i=0; i< hexStr.length/8;i++){
        const word = endianSwap(hexStr.slice(8 * i, 8 * i +8));
        const x = parseInt(word, 16);
        const w1 = x % n;
        const w2 = (Math.floor(x / n) + w1) % n;
        const w3 = (Math.floor(x / (n * n)) + w2) % n;
        out.push(wordList[w1], wordList[w2], wordList[w3]);
    }

    // 计算校验和
    const checksum = getChecksum(out.join(' '));
    out.push(checksum);

    return out.join(' ');
}

Monero 的助记词的推导方式 和 Bip39的最大区别是在 生成校验和时机

Monero其生成逻辑如下:

一 生成32字节(64位)随机熵,将其转成16进制格式 二 对这个随机熵按照每8位进行切割。进行端序调整后得到16进制字符串word 三 将word转换成为10进制整数x。 四 根据不同的算法,对x获取索引,然后根据索引到助记词库表查找对应的单词,添加到List里面 五 循环结束后,根据list里面的单词,按照一定的规则,计算校验和(取所有单词的前3个字节,拼接成一个字符串,进行crc32 hash 运算,得到非负整数,并取模,得到的结果作为索引再去助记词表中查单词,加到List中)

BIP39 的助记词生成规则

一 生成128~256位随机熵 二 根据这个随机熵,计算4/8位的校验和 三 将随机熵 + 校验和 拼接成一个新的16位的字符串 四 将字符串按照11位每段进行切割,每一段依次作为索引去助记词库找单词作为助记词 五 最后得到12 ~24 个的助记词列表

第二步 依据助记词导出私钥和公钥

Secret Spend Key(私密消费密钥)

● 作用:secret_spend_key 是你的私密消费密钥,类似于比特币的私钥,用于签署交易,以证明你对某个 Monero 地址的所有权。 ● 安全性:它是钱包中最重要的密钥,如果泄露,任何人都可以花费你钱包中的 Monero,因此需要妥善保管。 ● 用法:当用户发起交易时,secret_spend_key 会生成相应的签名,允许花费这些 Monero。

Secret View Key(私密查看密钥)

● 作用:secret_view_key 用于查看账户中的交易情况。它允许持有者解密和查看所有交易信息,而不会影响钱包的安全性。 ● 安全性:泄露此密钥不会影响 Monero 的花费权限。很多 Monero 观察钱包(view-only wallet)使用 secret_view_key 以查看余额和交易记录。 ● 用法:商家和第三方钱包可以使用 secret_view_key 追踪收款情况,但不会暴露对资金的控制权。

Public Spend Key(公有消费密钥)

● 作用:public_spend_key 是与 secret_spend_key 对应的公钥,用来生成 Monero 地址的一个部分。 ● 安全性:这是一个公开的信息,不需要加密保护。它是 Monero 地址的一部分,不会透露任何交易信息或钱包余额。 ● 用法:在生成 Monero 地址时,将 public_spend_key 和 public_view_key 结合在一起,形成完整的 Monero 地址。

Public View Key(公有查看密钥)

● 作用:public_view_key 是与 secret_view_key 对应的公钥,用来生成 Monero 地址的另一部分。 ● 安全性:public_view_key 是公开信息,用于生成地址,而不会透露任何其他信息。 ● 用法:和 public_spend_key 结合后生成一个唯一的 Monero 地址。

在monero-ts这个SDK中,它其实已经帮我们封装好了指定的生成逻辑,因此,我们只需要调用它的SDK即可

示例一 依据助记词导出公私钥和地址

与传统的BIP44 协议不一样的地方是,这边的Seed,其实传的就是助记词。不需要进行转换(这个坑钻研了很久)

const config = new MoneroWalletConfig();
// 只需要助记词 + 网络类型 即可
config.setSeed(mnemonic);
config.setNetworkType('mainnet')

const wallet = await MoneroWalletFull.createWallet(config);

const address = await wallet.getPrimaryAddress()
const publicSpendKey = await wallet.getPublicSpendKey();
const privateSpendKey = await wallet.getPrivateSpendKey();
const publicViewKey = await wallet.getPublicViewKey();
const privateViewKey = await wallet.getPrivateViewKey()

示例二 直接生成随机钱包

// 只需要网络类型 + 助记词语言类型
let walletKeys = await moneroTs.createWalletKeys({networkType: moneroTs.MoneroNetworkType.MAINNET, language: "English"});
const mnemonic = await walletKeys.getSeed();
const address = await walletKeys.getPrimaryAddress();
const spendKey = await walletKeys.getPublicSpendKey();
const privateKey = await walletKeys.getPrivateSpendKey();

console.log("Seed phrase: " + mnemonic);
console.log("Address: " + address); // get address of account 0, subaddress 0
console.log("Spend key: " + spendKey);
console.log("Private key: " + privateKey);

第三步 找到一个公共的RPC 节点

值得注意的是,在Monero 链上,RPC 节点分为两种 Daemon RPC Node 这个可以通过https://monero.fail/?chain=monero&network=mainnet 来获取 Wallet RPC Node 一般找不到,需要的话要自己搭建。因此通过Postman请求balance,签名,发送交易几乎无解(在没有自建节点的情况下),但是,我们可以变相的通过SDK来对其进行模拟,也能支持交易的发送

第四步 执行离线签名并实际发送

针对发送交易,monero提供了两种的发送方式

方式一 广播已签名的交易

relayTx(tx) 常见与热钱包进行交易处理的情况 核心处理逻辑 一 获取 钱包的助记词,path,网络类型,密码参数 二 设置钱包的连接管理器,多设置几个PRC节点 三 开启钱包 四 设置Daemon节点 五 调用wallet.createTx创建一笔交易,该交易是已经签完名了的 六 直接调用wallet.relayTx(tx)广播交易

export async function createAndSendMeneroTransaction(){
    // 根据密码打开当前的钱包
    const config = new MoneroWalletConfig();
    // config.setSeed(mnemonic);
    config.setPath("/Users/zhulida/Monero/wallets/Leo/Leo")
    // 需要被加密
    config.setPassword(process.env.MENERO_PASSWORD)
    config.setNetworkType('mainnet')

    let connectionManager = new MoneroConnectionManager();
    await connectionManager.addConnection({uri: "http://137.220.120.19:18089",priority: 1});
    await connectionManager.addConnection({uri: "http://node.community.rino.io:18081"});
    await connectionManager.addConnection({uri: "xmr.monopolymoney.eu:18089"});
    config.setConnectionManager(connectionManager)

    const wallet = await MoneroWalletFull.openWallet(config);

    // const walletProxy =await wallet.getWalletProxy();
    // console.log(walletProxy)
    wallet.setDaemonConnection({uri: "http://137.220.120.19:18089"})

    const balance = await wallet.getBalance();
    const pvk = await wallet.getPublicViewKey();
    console.log(balance,pvk)

    // await wallet.setConnectionManager(connectionManager)
    // 广播交易
    let tx = await wallet.createTx({
        accountIndex: 0,
        address: "89PmbkZCu2s4xpkuYYHsUTGiUnpsNsaxFCqEF2J322NacY183Najv1MPxaGa7oc4XMf6dJ2Eo7fKNjZPp2SgV5sGDxxn68z",
        amount: 1000000000n,
        priority: MoneroTxPriority.NORMAL,
    })

    console.log(tx);

    return await wallet.relayTx(tx);

}

方式二 提交一个新的交易,在确认有效后自动广播

submitTx(tx)常见于离线签名的场景 其核心处理逻辑如下 一 根据 助记词 和 网络类型 创建离线钱包 (不用设置节点) 二 根据 网络类型,PRIVATE_VIEW_KEY,主账户地址,和块高度,节点地址 生成 ViewOnly钱包 三 设置Daemon节点连接信息 四 对钱包进行同步处理 五 同步结束后,使用ViewOnly钱包创建一笔交易(因为没有PRIVIEW_SPEND_KEY,所以该交易还未被签名) 六 采用 离线钱包对 交易进行签名 七 签名后,再使用ViewOnly钱包发送消息submitTx

export async function signTransaction(params:any) {
    const start = Date.now();
    // 生成一个离线钱包(用来离线签)
    const { mnemonic } = params;
    const config = new MoneroWalletConfig();
    config.setSeed(mnemonic);
    // config.setPath('/Users/zhulida/Monero/wallets/Leo/temp/Offline');
    config.setNetworkType(MoneroNetworkType.MAINNET)
    // const offlineWallet = await MoneroWalletFull.openWallet(config);
    let offlineWallet = await moneroTs.createWalletFull(config);
    const initOfflineCost = Date.now();
    console.log("离线钱包创建完成,耗时:",initOfflineCost-start);

    // 生成一个viewOnly钱包(用来创建交易)
    // create and sync view-only wallet without spend key

    const viewOnlyConfig = new MoneroWalletConfig();
    // viewOnlyConfig.setPath('/Users/zhulida/Monero/wallets/Leo/temp/ViewOnly');
    viewOnlyConfig.setNetworkType(MoneroNetworkType.MAINNET);
    viewOnlyConfig.setPrimaryAddress("46YkJDmikFfBAv61FH9pQsbSPm8K2nbT2eQ4gsgSCSi2jN2aE2YhjjQ39RVhxpAqzo4xoHQcGMbWzVqNnepGd3aoSevHHkU");
    viewOnlyConfig.setPrivateViewKey(process.env.PRIVATE_VIEW_KEY);
    viewOnlyConfig.setRestoreHeight(3260750);

    viewOnlyConfig.setServer("http://137.220.120.19:18089")

    let viewOnlyWallet =await moneroTs.createWalletFull(viewOnlyConfig);
    // let viewOnlyWallet = await MoneroWalletFull.openWallet(viewOnlyConfig);
    const initViewOnly = Date.now();
    console.log("viewOnly钱包创建完成,耗时",initViewOnly-initOfflineCost);
    await viewOnlyWallet.setDaemonConnection("http://137.220.120.19:18089")
    await viewOnlyWallet.sync();
    const sync = Date.now();
    console.log("钱包同步完成,耗时",sync-initViewOnly)

    const balance = await viewOnlyWallet.getBalance();
    const pvk = await viewOnlyWallet.getPublicViewKey();
    console.log(balance,pvk)

    const tx = await viewOnlyWallet.createTx({
        accountIndex: 0,
        address: "89PmbkZCu2s4xpkuYYHsUTGiUnpsNsaxFCqEF2J322NacY183Najv1MPxaGa7oc4XMf6dJ2Eo7fKNjZPp2SgV5sGDxxn68z",
        amount: 2000000000n,
    })
    const createTxConst = Date.now();
    console.log("创建交易完成,耗时",createTxConst-sync)

    // console.log(tx)

    let signedTxSet = await offlineWallet.signTxs(tx.txSet.unsignedTxHex);
    console.log("离线签名完成,耗时",Date.now()-createTxConst)

    console.log(signedTxSet)

    const txHash = await viewOnlyWallet.submitTxs(signedTxSet.getSignedTxHex())
    console.log("交易总耗时",Date.now()-start)
    return txHash
}
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Leo
Leo
江湖只有他的大名,没有他的介绍。