Web钱包私钥导出新旧两种地址格式(加班到深夜修改的一个功能点)
前言:
这个问题是中午的时候,被用户反馈,当时还不觉得有问题。
调到最后发现一个小坑是:私钥地址,带0x和不带0x是有区别的。我们的Web钱包不要带0x,否则会还原出另一个新地址。
1. jssdk修改
(1)用私钥还原地址
/** 新地址 * Get address from private key. * @param {string} privateKeyHex the private key hexstring * @param {string} prefix address prefix * @return {string} address */ export const getAddressFromPrivateKey = (privateKeyHex, prefix) => { return getAddressFromPubKey(getPubKeyHexFromPrivateKey(privateKeyHex), prefix) } // 旧地址 Legacy export const getAddressFromPrivateKeyLegacy = (privateKeyHex, prefix) => { return getAddressFromPublicKeyLegacy(getPubKeyHexFromPrivateKey(privateKeyHex), prefix) } /** 新方法 * Gets address from pubKey with hex format. * @param {string} publicKey publicKey hexstring * @param {string} prefix address prefix * @return {string} address */ export const getAddressFromPubKey = (publicKey, prefix) => { publicKey = publicKey.slice(0, 2) === '0x' ? publicKey.slice(2) : publicKey const publicKey1 = Buffer.from(publicKey, 'hex') publicKey = Buffer.from(secp256k1.publicKeyConvert(new Uint8Array(publicKey1), false)).slice(1) const hash = createKeccakHash('keccak256').update(publicKey).digest() return encodeAddressToBech32(hash.slice(-20).toString('hex'), prefix) } // 旧的方法Legacy export const getAddressFromPublicKeyLegacy = (publicKeyHex, prefix) => { // const pubKey = ec.keyFromPublic(publicKeyHex, "hex") // const pubPoint = pubKey.getPublic() // const compressed = pubPoint.encodeCompressed() // const hexed = ab2hexstring(compressed) const hash = sha256ripemd160(publicKeyHex) // https://git.io/fAn8N const address = encodeAddress(hash, prefix) return address } export const encodeAddress = (value, prefix = "ex", type = "hex") => { const words = bech32.toWords(Buffer.from(value, type)) return bech32.encode(prefix, words) }
(2)新旧两种地址在转账交易类型
设置账户信息,也要体现两种地址
/** * @param {string} privateKey * @param {string} prefix * @return {OKEXChainClient} */ async setAccountInfo(privateKey, prefix = "ex", isPrivatekeyOld = 0) { if(!privateKey) { const address = await wallet.getAddress(); if (!address) throw new Error("invalid privateKey: " + privateKey) await this.setAccountInfoByWallet(address); return this; } if (privateKey !== this.privateKey) { let address = crypto.getAddressFromPrivateKey(privateKey, prefix) if (isPrivatekeyOld) { address = crypto.getAddressFromPrivateKeyLegacy(privateKey, prefix) } if (!address) throw new Error("invalid privateKey: " + privateKey) if (address === this.address) return this this.privateKey = privateKey this.address = address const data = await this.getAccount(address) this.account_number = this.getAccountNumberFromAccountInfo(data) } return this }
构造两种交易:"tendermint/PubKeySecp256k1” 和"ethermint/PubKeyEthSecp256k1",及两种不同的签名。
// PrivatekeyHex if (typeof privateKeyHexOrSigner === 'string') { const jsonStr = JSON.stringify(signMsg) const signBytes = Buffer.from(jsonStr) const privateKey = Buffer.from(privateKeyHexOrSigner, "hex") const signature = isPrivatekeyOldAddress ? crypto.signPrivateKeyOldAddress(signBytes.toString("hex"), privateKey) : crypto.sign(signBytes.toString("hex"), privateKey) const pubKey = crypto.encodePubKeyToCompressedBuffer(crypto.getPubKeyFromPrivateKey(privateKey)) signatures = [{ pub_key: { type: isPrivatekeyOldAddress ? "tendermint/PubKeySecp256k1" : "ethermint/PubKeyEthSecp256k1", value:pubKey}, signature: signature, }] } /** 旧签名 * Generates a signature (64 byte <r,s>) for a transaction based on given private key. * @param {string} signBytesHex - Unsigned transaction sign bytes hexstring. * @param {string | Buffer} privateKey - The private key. * @return {Buffer} Signature. Does not include tx. */ export const signPrivateKeyOldAddress = (signBytesHex, privateKey) => { const msgHash = sha256(signBytesHex) const msgHashHex = Buffer.from(msgHash, "hex") const signature = ecc.sign(msgHashHex, Buffer.from(privateKey, "hex")) // enc ignored if buffer return signature } /** 新签名 * Sign msg with privateKey and Msg in hex format. * @param {string} msgHex msg in hex format. * @param {string} privateKey - The private key in hex format. * @return {Buffer} Signature. */ export const sign = (msgHex, privateKey) => { const msg = Buffer.from(msgHex, "hex") const msgHash = createKeccakHash('keccak256').update(msg).digest() const signature = ecc.sign(msgHash, Buffer.from(privateKey, "hex")) // enc ignored if buffer return signature }
2. Web前端修改
(1)还原地址处
validatePrivateKey = async () => { try { let { privateKey, password } = this.state; privateKey = privateKey.replace(/^(0x)/, ''); if (!/^[\d|a-f]{64}$/.test(privateKey)) { throw new Error('not pass the reg'); } const oldAddress = crypto.getAddressFromPrivateKeyLegacy(privateKey); const newAddress = crypto.getAddressFromPrivateKey(privateKey); const oldBalance = await this.fetchAcount(oldAddress); const newBalance = await this.fetchAcount(newAddress); this.setState({ isValidatedPrivateKey: true, buttonLoading: false, isNone: false, oldAddress, newAddress: crypto.convertBech32ToHex(newAddress)[0], oldBalance: calc.showFloorTruncation(oldBalance), newBalance: calc.showFloorTruncation(newBalance), step: 2, }); const keyStore = crypto.generateKeyStore(privateKey, password); walletUtil.setUserInSessionStroage(privateKey, keyStore); this.props.commonAction.setPrivateKey(privateKey); // util.go(DesktopTypeMenu.current ? DesktopTypeMenu.current.url : void 0); } catch (e) { this.setState({ isValidatedPrivateKey: false, buttonLoading: false, isNone: false, }); } };
// 保留最后的尊严,哈哈哈 selectPathType = (type) => { window.localStorage.setItem(env.envConfig.privatekeyPathType, type); if (type === 'old') { window.localStorage.setItem( env.envConfig.privatekeyOldPathPrivatekey, this.state.privateKey ); } else { window.localStorage.setItem( env.envConfig.privatekeyOldPathPrivatekey, '' ); } this.setState({ pathType: type, }); };
账户部分
setUserInSessionStroageByWalletConnect(addr) { const user = { addr, }; window.localStorage.setItem(env.envConfig.dexUser, JSON.stringify(user)); window.OK_GLOBAL.senderAddr = addr; window.OK_GLOBAL.isLogin = true; }, // 重点是 setUserInSessionStroage(privateKey, keyStore) { const privatekeyPathType = window.localStorage.getItem( env.envConfig.privatekeyPathType ); let addr = crypto.getAddressFromPrivateKey(privateKey); if (privatekeyPathType === 'old') { addr = crypto.getAddressFromPrivateKeyLegacy(privateKey); } const user = { addr, info: keyStore, }; window.localStorage.setItem(env.envConfig.dexUser, JSON.stringify(user)); window.OK_GLOBAL.senderAddr = addr; window.OK_GLOBAL.generalAddr = crypto.convertBech32ToHex(addr)[0]; window.OK_GLOBAL.isLogin = true; },
还有URL部分,涉及到跨域。
export const DEFAULT = window.okGlobal.mainDomain || 'https://www.okx.com'; export function getApiUrl(apiUrl = DEFAULT) { const protocol = window.location.protocol; if (/file/.test(protocol) || window.location.hostname === '127.0.0.1') return apiUrl; return `${window.location.protocol}//${window.location.host}`; } export function getCurrentApiUrl() { // let url = getApiUrl(); let url = 'https://www.okx.com'; const currentNode = storage.get('currentNode'); if (currentNode && currentNode.httpUrl) url = currentNode.httpUrl; return url; }
(2)发送交易处
const isPrivatekeyOld = oldPrivate ? 1 : 0; okexchainClient .setAccountInfo(privateKey, 'ex', isPrivatekeyOld) .then(() => { okexchainClient .sendSendTransaction( address, amountStr, symbol, note, null, isPrivatekeyOld ) .then((res) => { if (res.result.code) { this._transferErr( toLocale(`error.code.${res.result.code}`) || toLocale('trans_fail') ); } else { this._transferSuccess(onSuccess); } }) .catch(() => { this._transferErr(toLocale('trans_fail')); }); });