# 代码错误如何导致我亏损2400个AR币的经历

By [Evan JIANG](https://paragraph.com/@firstfan) · 2024-06-11

---

作为一个程序员，在币圈都想成为科学家，今年才开始写脚本做链上交互。我取得了一些收获，很快就获得了一些空投收入。五月后，没什么有趣项目，我想看看AR，因为最近还比较火，我去年就买了2400+多个AR币，想着是否要把AR币转到链上钱包，以获取可能的AO空投收益。

其实五月初就开始看，一直有个问题解决不了，就是同样助记词无法获得和ARConnect等钱包一致的地址，我看了ARConnect的源码，从助记词转换到Jwk文件，而官方SDK只支持从Jwk文件获得地址和进行签名等操作，并不支持直接从助记词生成钱包或者jwk文件。

ARConnect对应的源码地址在

[https://github.com/arconnectio/ArConnect/blob/production/src/wallets/generator.ts](https://github.com/arconnectio/ArConnect/blob/production/src/wallets/generator.ts)

    /**
     * Credits to arweave.app for the mnemonic wallet generation
     *
     * https://github.com/jfbeats/ArweaveWebWallet/blob/master/src/functions/Wallets.ts
     * https://github.com/jfbeats/ArweaveWebWallet/blob/master/src/functions/Crypto.ts
     */
    
    /**
     * Generate a JWK from a mnemonic seedphrase
     *
     * @param mnemonic Mnemonic seedphrase to generate wallet from
     * @returns Wallet JWK
     */
    export async function jwkFromMnemonic(mnemonic: string) {
      const { privateKey } = await getKeyPairFromMnemonic(
        mnemonic,
        {
          id: "rsa",
          modulusLength: 4096
        },
        { privateKeyFormat: "pkcs8-der" }
      );
      const jwk = pkcs8ToJwk(privateKey as any);
    
      return jwk;
    }
    
    /**
     * Convert a PKCS8 private key to a JWK
     *
     * @param privateKey PKCS8 private key to convert
     * @returns JWK
     */
    async function pkcs8ToJwk(privateKey: Uint8Array): Promise<JWKInterface> {
      const key = await window.crypto.subtle.importKey(
        "pkcs8",
        privateKey,
        { name: "RSA-PSS", hash: "SHA-256" },
        true,
        ["sign"]
      );
      const jwk = await window.crypto.subtle.exportKey("jwk", key);
    
      return {
        kty: jwk.kty!,
        e: jwk.e!,
        n: jwk.n!,
        d: jwk.d,
        p: jwk.p,
        q: jwk.q,
        dp: jwk.dp,
        dq: jwk.dq,
        qi: jwk.qi
      };
    }
    

实际上代码还挺简单的，但一直生成的地址不对。通过网上搜索，我认为问题在于它调用了window.crypto.subtle，而非非浏览器环境中的crypto.subtle。可能中间格式不一致。我研究了两天没什么结果就放一边了。

[https://stackoverflow.com/a/62990139/1304867](https://stackoverflow.com/a/62990139/1304867)

5月30日，有消息说AO要空投啦，应该把AR从交易所转到链上，我习惯于所有操作都多钱包，本来想分几百个钱包存一下的，所以就没想过用浏览器钱包一个个操作。当时想到，虽然生成的地址和ARConnect不一致，但反正一个助记词对应一个地址，那最多以后也只用代码来重新归集这些AR币。

我使用官方jssdk写代码首先测试了下从A钱包转到B钱包，从B钱包转到C钱包，只用了0.5AR做测试，程序调空没有问题，链上浏览器看到状态都成功，觉得可以实施了。这里提供A钱包地址，大家可以跟踪看到测试过程，也能看到后续转错的交易：82X057LFL7CYkzwmKOQTuPPcgOd\_rTmULfLkJzGoaoY。就从CEX提取了2400AR到第一个钱包，开始转移。AR链不管生成钱包还是转一笔币都很慢。但执行到转移到第二个钱包后，我当时马上就注意到不对了，因为我上轮测试大概对这些地址有些印象，第二个钱包应该是个oo开头的地址。这里又是我一个特别大的错误，调试过程中，我本身打印了所有钱包信息，有输出jwk文件到log，但测试后觉得没问题，log实在太长，会影响查看进度，就把这些log语句注释掉了。但此时已经晚了，交易已将2398个币转入了下一个地址，在当时正好价值10万USDT。

[https://viewblock.io/arweave/tx/WGzn7lmYMTw6Zw-oMoOOwkXbTDpRubYMlDYEdEIa4RA](https://viewblock.io/arweave/tx/WGzn7lmYMTw6Zw-oMoOOwkXbTDpRubYMlDYEdEIa4RA)

这时候我停下来要看看到底是为什么会地址不一致，而且A钱包地址是正确的。

那么我就开始测试，是否生成地址是稳定的。（代码中的助记词是新生成测试用的）

    import Arweave from "arweave";
    import * as bip39 from 'bip39';
    import {getKeyPairFromMnemonic, getKeyPairFromSeed} from "human-crypto-keys";
    import {webcrypto} from "crypto"
    
    const arweave = Arweave.init({
        host: 'arweave.net',
        port: 443,
        protocol: 'https'
    });
    const keys = [];
    
    export async function jwkFromMnemonic(mnemonic) {
        let seedBuffer = await bip39.mnemonicToSeed(mnemonic);
        let seed = new Uint8Array(seedBuffer.buffer);
        const { privateKey } = await getKeyPairFromSeed(
            seed,
            {
                id: "rsa",
                modulusLength: 4096
            },
            { privateKeyFormat: "pkcs8-der" }
        );
        // console.log(privateKey);
    
        let key = await webcrypto.subtle.importKey(
            "pkcs8",
            privateKey,
            { name: "RSA-PSS", hash: "SHA-256" },
            true,
            ["sign"]
        );
        // console.log(key);
        const jwk = await webcrypto.subtle.exportKey("jwk", key);
        // console.log(jwk);
        return {
            'kty': jwk.kty,
            'e': jwk.e,
            'n': jwk.n,
            'd': jwk.d,
            'p': jwk.p,
            'q': jwk.q,
            'dp': jwk.dp,
            'dq': jwk.dq,
            'qi': jwk.qi
        };
    }
    
    async function main() {
        const mn = 'teach grab street first maze tip assault family unfold mistake mean weasel';
        for (let i=0;i<3;i++) {
            let key = await jwkFromMnemonic(mn);
            let curr_addr = await arweave.wallets.jwkToAddress(key);
            console.log(curr_addr);
        }
    }
    
    await main();
    

在MacOS + WebStorm环境下，这代码对同一个助记词，生成三次地址。多次运行后，第一个结果总是一致的，而后面的结果会发生变化，以下我给出我这里两次运行的结果（这里不确定为什么第二个输出也一致，就是很不稳定）

第一次：yFyxBemQEZWvQsDSxORR-BbHLTnNIIMWl1tzBqKhURU 1Hac552aQktwuS\_ovaEsV9ccMV4IjsZrbZFMK7RuFlw dqn65rI3XRuZ0sObxw4JoPRjfBCJOEF6wLSJ2UqUEdg

第二次：yFyxBemQEZWvQsDSxORR-BbHLTnNIIMWl1tzBqKhURU 1Hac552aQktwuS\_ovaEsV9ccMV4IjsZrbZFMK7RuFlwFABeY-3K4cR-d1ghvHZpYJdYi0aaWJPGSBJWw4ZvM3c

然后我就开始看，到底是哪一步导致这个问题的，最开始我完全没有怀疑到getKeyPairFromMnemonic这个方法来自于getKeyPairFromMnemonic这个库，这个库都是五年前的了，一般认为这么久没有改动应该很稳定。

[https://github.com/ipfs-shipyard/js-human-crypto-keys](https://github.com/ipfs-shipyard/js-human-crypto-keys)

但在反复调试过程中，发现就是这一步返回的值就是不稳定的。通过源码更精确的来看，是在

    let seedBuffer = await bip39.mnemonicToSeed(mnemonic);
    let seed = new Uint8Array(seedBuffer.buffer);
    

这里返回就是不一致的，因为python打印默认不会打印几千位的数组全部内容，一开始比较头部几百位看起来一致，所以定位问题还花了不少时间。实际上在我这里的情况是，八千多位的数组，前六千多位每次都是一致的，后面看起来是脏数据。那么也解释了为什么每次程序刚运行都是稳定的，因为内存片区应该都是0. 但这个库很奇怪，完全没有初始化或者destroy方法，也就是不觉得需要重新调用初始化。

当我再认真看时，就发现这个human-crypto-keys项目只有32个star，并且这个bug在去年就被提了issue

[https://github.com/ipfs-shipyard/js-human-crypto-keys/issues/28](https://github.com/ipfs-shipyard/js-human-crypto-keys/issues/28)

我是觉得我照着ARConnect代码直接复制，完全没有想到会再去一个个看它引用的库的情况。

总结这次经历：首先，我的测试不够充分，如果多做一轮打印余额的检查也能复现问题；二是即便代码没问题，也不该总是直接用大金额的token直接一次性操作，可以分成几次；三是所有log还是尽可能打印，如果输出了jwk文件内容就可以恢复了。

然而，我也想对ARweave项目提出批评，这个项目的官方SDK本身完全没考虑提供从助记词创建钱包的方法，并且，相关的web钱包实际上都用了上面同一套代码，我没有继续研究为什么在web钱包中没有出错，但看起来使用了human-crypto-keys库都是有潜在风险的。也让我怀疑，到底是多少真实开发者在ARweave链上做开发。

最后更新，因为发布文章，ArConnect团队联系了我，我们一起定位了问题，我也提了修改的PR。ArConnect团队按照发现bug报告的奖励给了我1200个AR，挽回了我一半的损失，在此表示感谢。

---

*Originally published on [Evan JIANG](https://paragraph.com/@firstfan/2400-ar)*
