diff --git a/src/@types/tiny-secp256k1.d.ts b/src/@types/tiny-secp256k1.d.ts deleted file mode 100644 index 78de42f2..00000000 --- a/src/@types/tiny-secp256k1.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "tiny-secp256k1"; diff --git a/src/api/ergo/addresses.ts b/src/api/ergo/addresses.ts index e5addce6..8f39fdd7 100644 --- a/src/api/ergo/addresses.ts +++ b/src/api/ergo/addresses.ts @@ -1,7 +1,7 @@ -import { P2PK_TREE_PREFIX, NETWORK } from "@/constants/ergo"; +import { NETWORK, P2PK_TREE_PREFIX } from "@/constants/ergo"; import { ErgoBoxCandidate } from "@/types/connector"; +import { last } from "@fleet-sdk/common"; import { ErgoAddress } from "@fleet-sdk/core"; -import { last } from "lodash-es"; export function getChangeAddress( outputs: ErgoBoxCandidate[], diff --git a/src/api/ergo/eip28.ts b/src/api/ergo/eip28.ts index 6de78fb7..11190489 100644 --- a/src/api/ergo/eip28.ts +++ b/src/api/ergo/eip28.ts @@ -1,3 +1,5 @@ +import { hex, randomBytes } from "@fleet-sdk/crypto"; + /** * Creates a EIP-28 signing message * @param message @@ -5,8 +7,6 @@ * @returns the signing message formatted as "signingMessage;origin;timestamp;randomBytes" */ export function buildEip28ResponseMessage(message: string, origin: string): string { - const buffer = Buffer.from(new Uint8Array(32)); - crypto.getRandomValues(buffer); - - return `${message};${origin};${Math.floor(Date.now() / 1000)};${buffer.toString("hex")}`; + const rand = hex.encode(randomBytes(32)); + return `${message};${origin};${Math.floor(Date.now() / 1000)};${rand}`; } diff --git a/src/api/ergo/extraction.ts b/src/api/ergo/extraction.ts new file mode 100644 index 00000000..79ae11bc --- /dev/null +++ b/src/api/ergo/extraction.ts @@ -0,0 +1,65 @@ +import { P2PK_TREE_PREFIX, PK_HEX_LENGTH, SIGMA_CONSTANT_PK_MATCHER } from "@/constants/ergo"; +import { Registers } from "@/types/connector"; +import { ErgoTree } from "ergo-lib-wasm-browser"; +import { Box, isEmpty, uniq } from "@fleet-sdk/common"; +import { addressFromErgoTree, addressFromPk } from "./addresses"; + +export function extractPksFromRegisters(registers: Registers): string[] { + const pks: string[] = []; + for (const register of Object.values(registers)) { + const pk = extractPkFromSigmaConstant(register); + if (pk) pks.push(pk); + } + + return pks; +} + +export function extractPksFromP2SErgoTree(ergoTree: string): string[] { + const pks: string[] = []; + const tree = ErgoTree.from_base16_bytes(ergoTree); + const len = tree.constants_len(); + for (let i = 0; i < len; i++) { + const constant = tree.get_constant(i)?.encode_to_base16(); + const pk = extractPkFromSigmaConstant(constant); + if (pk) pks.push(pk); + } + + return pks; +} + +export function extractPkFromSigmaConstant(constant?: string): string | undefined { + if (!constant) return; + + const result = SIGMA_CONSTANT_PK_MATCHER.exec(constant); + if (!result) return; + + for (let i = 0; i < result.length; i++) { + if (result[i] && result[i].length === PK_HEX_LENGTH) { + return result[i]; + } + } +} + +function extractAddressesFromInput(input: Box): string[] { + if (input.ergoTree.startsWith(P2PK_TREE_PREFIX)) { + return [addressFromErgoTree(input.ergoTree)]; + } + + let pks = extractPksFromP2SErgoTree(input.ergoTree); + if (input.additionalRegisters) { + pks = pks.concat(extractPksFromRegisters(input.additionalRegisters)); + } + + if (isEmpty(pks)) return []; + + const addresses: string[] = []; + for (const pk of uniq(pks)) { + addresses.push(addressFromPk(pk)); + } + + return addresses; +} + +export function extractAddressesFromInputs(inputs: Box[]) { + return inputs.map((input) => extractAddressesFromInput(input)).flat(); +} diff --git a/src/api/ergo/hdKey.ts b/src/api/ergo/hdKey.ts index c1fbb104..25261d91 100755 --- a/src/api/ergo/hdKey.ts +++ b/src/api/ergo/hdKey.ts @@ -52,7 +52,7 @@ export default class HdKey { } public get extendedPublicKey(): Uint8Array { - if (this.#xpk) { + if (!this.#xpk) { const decoded = base58check.decode(this.#change.extendedPublicKey); this.#xpk = this.normalizeExtendedKey(decoded); } diff --git a/src/api/ergo/serialization.ts b/src/api/ergo/serialization.ts new file mode 100644 index 00000000..51b0481d --- /dev/null +++ b/src/api/ergo/serialization.ts @@ -0,0 +1,10 @@ +import { isUndefined } from "@fleet-sdk/common"; +import { Coder } from "@fleet-sdk/crypto"; +import { parse } from "@fleet-sdk/serializer"; + +export function sigmaDecode(value: string, coder?: Coder) { + const v = parse(value, "safe"); + if (isUndefined(v)) return; + + return coder ? coder.encode(v) : v; +} diff --git a/src/api/ergo/sigmaSerializer.ts b/src/api/ergo/sigmaSerializer.ts deleted file mode 100644 index f36727d5..00000000 --- a/src/api/ergo/sigmaSerializer.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - COLL_BYTE_PREFIX, - MIN_COLL_LENGTH, - P2PK_TREE_PREFIX, - PK_HEX_LENGTH, - SIGMA_CONSTANT_PK_MATCHER -} from "@/constants/ergo"; -import { Registers, UnsignedInput } from "@/types/connector"; -import { ErgoTree } from "ergo-lib-wasm-browser"; -import { isEmpty, uniq } from "@fleet-sdk/common"; -import { addressFromErgoTree, addressFromPk } from "./addresses"; - -export function isColl(input: string): boolean { - return !isEmpty(input) && input.startsWith(COLL_BYTE_PREFIX) && input.length >= MIN_COLL_LENGTH; -} - -export function decodeColl(input: string, encoding: BufferEncoding = "utf8"): string | undefined { - if (!isColl(input)) { - return; - } - - return decodeConst(input, COLL_BYTE_PREFIX.length, encoding); -} - -function decodeConst( - input: string, - position: number, - encoding: BufferEncoding -): string | undefined { - const [start, length] = getCollSpan(input, position); - if (!length) { - return; - } - - return Buffer.from(input.slice(start, start + length), "hex").toString(encoding); -} - -function getCollSpan(input: string, start: number): [start: number, length: number | undefined] { - return decodeVlq(input, start); -} - -function decodeVlq(input: string, position: number): [cursor: number, value: number | undefined] { - let len = 0; - let readNext = true; - do { - const lenChunk = parseInt(input.slice(position, (position += 2)), 16); - if (isNaN(lenChunk)) { - return [position, undefined]; - } - - readNext = (lenChunk & 0x80) !== 0; - len = 128 * len + (lenChunk & 0x7f); - } while (readNext); - - return [position, len * 2]; -} - -export function extractPksFromRegisters(registers: Registers): string[] { - const pks: string[] = []; - for (const register of Object.values(registers)) { - const pk = extractPkFromSigmaConstant(register); - if (pk) pks.push(pk); - } - - return pks; -} - -export function extractPksFromP2SErgoTree(ergoTree: string): string[] { - const pks: string[] = []; - const tree = ErgoTree.from_base16_bytes(ergoTree); - const len = tree.constants_len(); - for (let i = 0; i < len; i++) { - const constant = tree.get_constant(i)?.encode_to_base16(); - const pk = extractPkFromSigmaConstant(constant); - if (pk) { - pks.push(pk); - } - } - - return pks; -} - -export function extractPkFromSigmaConstant(constant?: string): string | undefined { - if (!constant) { - return; - } - - const result = SIGMA_CONSTANT_PK_MATCHER.exec(constant); - if (!result) { - return; - } - - for (let i = 0; i < result.length; i++) { - if (result[i] && result[i].length === PK_HEX_LENGTH) { - return result[i]; - } - } -} - -function extractAddressesFromInput(input: UnsignedInput): string[] { - if (input.ergoTree.startsWith(P2PK_TREE_PREFIX)) { - return [addressFromErgoTree(input.ergoTree)]; - } - - let pks = extractPksFromP2SErgoTree(input.ergoTree); - if (input.additionalRegisters) { - pks = pks.concat(extractPksFromRegisters(input.additionalRegisters)); - } - - if (isEmpty(pks)) { - return []; - } - - const addresses: string[] = []; - for (const pk of uniq(pks)) { - addresses.push(addressFromPk(pk)); - } - - return addresses; -} - -export function extractAddressesFromInputs(inputs: UnsignedInput[]) { - return inputs.map((input) => extractAddressesFromInput(input)).flat(); -} diff --git a/src/api/ergo/transaction/interpreter/outputInterpreter.ts b/src/api/ergo/transaction/interpreter/outputInterpreter.ts index b166fe5e..395e47c7 100644 --- a/src/api/ergo/transaction/interpreter/outputInterpreter.ts +++ b/src/api/ergo/transaction/interpreter/outputInterpreter.ts @@ -3,11 +3,12 @@ import { ErgoBoxCandidate } from "@/types/connector"; import { decimalize, toBigNumber } from "@/utils/bigNumbers"; import BigNumber from "bignumber.js"; import { find, findIndex, first, isEmpty } from "lodash-es"; -import { decodeColl } from "@/api/ergo/sigmaSerializer"; +import { sigmaDecode } from "@/api/ergo/serialization"; import { StateAssetInfo } from "@/types/internal"; import { isBabelContract } from "../../babelFees"; import { AddressType, ErgoAddress, Network } from "@fleet-sdk/core"; import { EIP12UnsignedInput } from "@fleet-sdk/common"; +import { utf8 } from "@fleet-sdk/crypto"; export type OutputAsset = { tokenId: string; @@ -155,15 +156,16 @@ export class OutputInterpreter { }; } - const decimals = parseInt(decodeColl(this._box.additionalRegisters["R6"]) ?? ""); + const decodedDecimals = sigmaDecode(this._box.additionalRegisters["R6"], utf8); + const decimals = decodedDecimals ? parseInt(decodedDecimals) : undefined; return { tokenId: token.tokenId, - name: decodeColl(this._box.additionalRegisters["R4"]) ?? "", + name: sigmaDecode(this._box.additionalRegisters["R4"], utf8) ?? "", decimals, amount: decimals ? decimalize(toBigNumber(token.amount), decimals) : toBigNumber(token.amount), - description: decodeColl(this._box.additionalRegisters["R5"]) ?? "", + description: sigmaDecode(this._box.additionalRegisters["R5"], utf8) ?? "", minting: true }; } diff --git a/src/api/ergo/transaction/interpreter/utils.ts b/src/api/ergo/transaction/interpreter/utils.ts index 7399b7c7..73ae436d 100644 --- a/src/api/ergo/transaction/interpreter/utils.ts +++ b/src/api/ergo/transaction/interpreter/utils.ts @@ -1,6 +1,6 @@ -import { Amount, TokenAmount } from "@fleet-sdk/common"; +import { Amount, Box, TokenAmount } from "@fleet-sdk/common"; import { OutputAsset } from "@/api/ergo/transaction/interpreter/outputInterpreter"; -import { ErgoBoxCandidate, Token, UnsignedInput } from "@/types/connector"; +import { ErgoBoxCandidate, Token } from "@/types/connector"; import { StateAssetInfo } from "@/types/internal"; import { decimalize, toBigNumber } from "@/utils/bigNumbers"; @@ -16,7 +16,7 @@ export const tokensToOutputAssets = (tokens: Token[], assetInfo: StateAssetInfo) }); }; -export const boxCandidateToBoxAmounts = (b: ErgoBoxCandidate | UnsignedInput) => { +export const boxCandidateToBoxAmounts = (b: ErgoBoxCandidate | Box) => { return { value: b.value.toString(), assets: b.assets diff --git a/src/api/ergo/transaction/prover.ts b/src/api/ergo/transaction/prover.ts index e8239f04..fb437605 100644 --- a/src/api/ergo/transaction/prover.ts +++ b/src/api/ergo/transaction/prover.ts @@ -4,10 +4,8 @@ import WebUSBTransport from "@ledgerhq/hw-transport-webusb"; import { Address, BlockHeaders, - ErgoBoxCandidate, ErgoBoxes, ErgoStateContext, - NonMandatoryRegisterId, PreHeader, SecretKey, SecretKeys, @@ -184,7 +182,7 @@ export class Prover { ergoTree: Buffer.from(wasmOutput.ergo_tree().sigma_serialize_bytes()), creationHeight: wasmOutput.creation_height(), tokens: mapTokens(wasmOutput.tokens()), - registers: this.serializeRegisters(wasmOutput) + registers: Buffer.from(wasmOutput.serialized_additional_registers()) }); } @@ -268,9 +266,9 @@ export class Prover { } private _signInputs( - unsigned: UnsignedTransaction, - unspentBoxes: ErgoBoxes, - dataInputBoxes: ErgoBoxes, + tx: UnsignedTransaction, + inputs: ErgoBoxes, + dataInputs: ErgoBoxes, headers: Header[], inputsToSign: number[] ) { @@ -288,17 +286,11 @@ export class Prover { ); const preHeader = PreHeader.from_block_header(blockHeaders.get(0)); - const signContext = new ErgoStateContext(preHeader, blockHeaders); + const context = new ErgoStateContext(preHeader, blockHeaders); const signed: SignedInput[] = []; for (const index of inputsToSign) { - const result = wallet.sign_tx_input( - index, - signContext, - unsigned, - unspentBoxes, - dataInputBoxes - ); + const result = wallet.sign_tx_input(index, context, tx, inputs, dataInputs); signed.push({ boxId: result.box_id().to_str(), @@ -309,24 +301,6 @@ export class Prover { return signed; } - private serializeRegisters(box: ErgoBoxCandidate): Buffer { - const registerEnum = NonMandatoryRegisterId; - if (!box.register_value(registerEnum.R4)) { - return Buffer.from([]); - } - - const registers = [ - Buffer.from(box.register_value(registerEnum.R4)?.sigma_serialize_bytes() ?? []), - Buffer.from(box.register_value(registerEnum.R5)?.sigma_serialize_bytes() ?? []), - Buffer.from(box.register_value(registerEnum.R6)?.sigma_serialize_bytes() ?? []), - Buffer.from(box.register_value(registerEnum.R7)?.sigma_serialize_bytes() ?? []), - Buffer.from(box.register_value(registerEnum.R8)?.sigma_serialize_bytes() ?? []), - Buffer.from(box.register_value(registerEnum.R9)?.sigma_serialize_bytes() ?? []) - ].filter((b) => b.length > 0); - - return Buffer.concat([...[Buffer.from([registers.length])], ...registers]); - } - private reportState(state: PartialSignState) { if (!this._callbackFn) { return; diff --git a/src/api/explorer/eip4Parser.ts b/src/api/explorer/eip4Parser.ts index a47c282d..9771c6b2 100644 --- a/src/api/explorer/eip4Parser.ts +++ b/src/api/explorer/eip4Parser.ts @@ -2,22 +2,16 @@ import { Registers } from "@/types/connector"; import { IAssetInfo } from "@/types/database"; import { AssetStandard, AssetSubtype, AssetType } from "@/types/internal"; import { Token } from "@ergo-graphql/types"; -import { SColl, SConstant, SPair, parse } from "@fleet-sdk/serializer"; -import { utf8, hex, Coder } from "@fleet-sdk/crypto"; -import { isUndefined, isEmpty } from "@fleet-sdk/common"; - -function decode(value: string, coder?: Coder) { - const v = parse(value, "safe"); - if (isUndefined(v)) return; - - return coder ? coder.encode(v) : v; -} +import { SColl, SConstant, SPair } from "@fleet-sdk/serializer"; +import { hex, utf8 } from "@fleet-sdk/crypto"; +import { isEmpty } from "@fleet-sdk/common"; +import { sigmaDecode } from "../ergo/serialization"; export function parseEIP4Asset(tokenInfo: Token): IAssetInfo | undefined { if (!tokenInfo.box) return; const registers = tokenInfo.box.additionalRegisters as Registers; - const type = decode(registers.R7, hex); + const type = sigmaDecode(registers.R7, hex); const assetInfo: IAssetInfo = { id: tokenInfo.tokenId, mintingBoxId: tokenInfo.boxId, @@ -33,7 +27,7 @@ export function parseEIP4Asset(tokenInfo: Token): IAssetInfo | undefined { }; if (assetInfo.type === AssetType.NFT) { - assetInfo.artworkHash = decode(registers.R8, hex); + assetInfo.artworkHash = sigmaDecode(registers.R8, hex); const r9 = SConstant.from(registers.R9); if (r9.type instanceof SColl) { diff --git a/src/components/WalletItem.vue b/src/components/WalletItem.vue index d8396a0b..4a71d1f9 100644 --- a/src/components/WalletItem.vue +++ b/src/components/WalletItem.vue @@ -13,18 +13,18 @@ $filters.walletType(wallet.type) }} @@ -34,6 +34,7 @@ import { defineComponent } from "vue"; import { walletChecksum } from "@emurgo/cip4-js"; import { renderIcon } from "@download/blockies"; import LoadingIndicator from "./LoadingIndicator.vue"; +import { hex } from "@fleet-sdk/crypto"; const mkcolor = (primary: string, secondary: string, spots: string) => ({ primary, @@ -51,6 +52,7 @@ const COLORS = [ export default defineComponent({ name: "WalletItem", + components: { LoadingIndicator }, props: { wallet: { type: Object, required: true }, loading: { type: Boolean, default: false }, @@ -73,7 +75,7 @@ export default defineComponent({ this.$nextTick(() => { const plate = walletChecksum(this.wallet.extendedPublicKey); this.checksum = plate.TextPart; - const colorIdx = Buffer.from(plate.ImagePart, "hex")[0] % COLORS.length; + const colorIdx = hex.decode(plate.ImagePart)[0] % COLORS.length; const color = COLORS[colorIdx]; renderIcon( { @@ -90,7 +92,6 @@ export default defineComponent({ } } } - }, - components: { LoadingIndicator } + } }); diff --git a/src/config/axiosConfig.ts b/src/config/axiosConfig.ts index fd2f1dd5..4b62bb9d 100644 --- a/src/config/axiosConfig.ts +++ b/src/config/axiosConfig.ts @@ -7,7 +7,6 @@ axios.defaults.transformResponse = [ try { data = JSONBig.parse(data); } catch (e) { - console.error(e); return data; } } diff --git a/src/constants/ergo.ts b/src/constants/ergo.ts index 9715a254..3bf5b3ba 100644 --- a/src/constants/ergo.ts +++ b/src/constants/ergo.ts @@ -13,8 +13,6 @@ export const MAINNET_MINER_FEE_TREE = "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304"; export const SIGMA_CONSTANT_PK_MATCHER = new RegExp(/08cd([0-9a-fA-F]{66})|^07([0-9a-fA-F]{66})$/); export const P2PK_TREE_PREFIX = "0008cd"; -export const COLL_BYTE_PREFIX = "0e"; -export const MIN_COLL_LENGTH = 4; export const PK_HEX_LENGTH = 66; export const UNKNOWN_MINTING_BOX_ID = ""; export const TOKEN_ID_LENGTH = 64; diff --git a/src/store.ts b/src/store.ts index c93bdbf9..f27e72c7 100755 --- a/src/store.ts +++ b/src/store.ts @@ -51,7 +51,7 @@ import { addressesDbService } from "@/api/database/addressesDbService"; import { assetsDbService } from "@/api/database/assetsDbService"; import AES from "crypto-js/aes"; import { connectedDAppsDbService } from "./api/database/connectedDAppsDbService"; -import { extractAddressesFromInputs as extractP2PKAddressesFromInputs } from "./api/ergo/sigmaSerializer"; +import { extractAddressesFromInputs } from "./api/ergo/extraction"; import { utxosDbService } from "./api/database/utxosDbService"; import { MIN_UTXO_SPENT_CHECK_TIME } from "./constants/intervals"; import { assetInfoDbService } from "./api/database/assetInfoDbService"; @@ -744,7 +744,7 @@ export default createStore({ await dispatch(ACTIONS.LOAD_MARKET_RATES); }, async [ACTIONS.SIGN_TX]({ state }, command: SignTxCommand) { - const inputAddresses = extractP2PKAddressesFromInputs(command.tx.inputs); + const inputAddresses = extractAddressesFromInputs(command.tx.inputs); const ownAddresses = await addressesDbService.getByWalletId(command.walletId); const addresses = ownAddresses .filter((a) => inputAddresses.includes(a.script)) @@ -803,17 +803,16 @@ export default createStore({ command: SignEip28MessageCommand ): Promise { const ownAddresses = await addressesDbService.getByWalletId(command.walletId); - const deriver = await HdKey.fromMnemonic( - await walletsDbService.getMnemonic(command.walletId, command.password) - ); + const mnemonic = await walletsDbService.getMnemonic(command.walletId, command.password); + const deriver = await HdKey.fromMnemonic(mnemonic); const signingAddress = ownAddresses.filter((x) => x.script === command.address); - const message = buildEip28ResponseMessage(command.message, command.origin); - const proofBytes = new Prover(deriver).from(signingAddress).signMessage(message); + const signedMessage = buildEip28ResponseMessage(command.message, command.origin); + const proof = new Prover(deriver).from(signingAddress).signMessage(signedMessage); return { - signedMessage: message, - proof: Buffer.from(proofBytes).toString("hex") + signedMessage, + proof: hex.encode(proof) }; }, async [ACTIONS.LOAD_CONNECTIONS]({ commit }) { diff --git a/vite.config.ts b/vite.config.ts index 0e37d271..638c0ea2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -50,8 +50,8 @@ export default defineConfig({ include: ["vue", "vue-router", "vuex"] }, server: { - port: 5173, + port: 5174, strictPort: true, - hmr: { port: 5173 } + hmr: { port: 5174 } } });