Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix validate address #354

Merged
merged 3 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/web3/src/api/node-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {
requestWithLog
} from './types'
import { Api as NodeApi, CallContractFailed, CallContractSucceeded } from './api-alephium'
import { tryGetCallResult } from '../contract'
import {
HexString,
addressFromContractId,
Expand All @@ -38,6 +37,7 @@ import {
isHexString,
toNonNegativeBigInt
} from '../utils'
import * as node from '../api/api-alephium'

function initializeNodeApi(baseUrl: string, apiKey?: string, customFetch?: typeof fetch): NodeApi<string> {
const nodeApi = new NodeApi<string>({
Expand Down Expand Up @@ -253,3 +253,10 @@ export class NodeProvider implements NodeProviderApis {
}
}
}

export function tryGetCallResult(result: node.CallContractResult): node.CallContractSucceeded {
if (result.type === 'CallContractFailed') {
throw new Error(`Failed to call contract, error: ${(result as node.CallContractFailed).error}`)
}
return result as node.CallContractSucceeded
}
2 changes: 1 addition & 1 deletion packages/web3/src/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

import 'cross-fetch/polyfill'
import { node } from '..'
import * as node from '../api/api-alephium'

export function convertHttpResponse<T>(response: { status: number; data: T; error?: { detail: string } }): T {
if (response.error) {
Expand Down
2 changes: 1 addition & 1 deletion packages/web3/src/codec/contract-output-codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Codec } from './codec'
import { Token, tokensCodec } from './token-codec'
import { ContractOutput as ApiContractOutput } from '../api/api-alephium'
import { blakeHash, createHint } from './hash'
import { binToHex, bs58 } from '..'
import { binToHex, bs58 } from '../utils'
import { signedIntCodec } from './signed-int-codec'
import { lockupScriptCodec } from './lockup-script-codec'

Expand Down
6 changes: 3 additions & 3 deletions packages/web3/src/codec/lockup-script-codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
*/
import { Buffer } from 'buffer/'
import { Parser } from 'binary-parser'
import { DecodedCompactInt, compactUnsignedIntCodec } from './compact-int-codec'
import { DecodedCompactInt, compactSignedIntCodec } from './compact-int-codec'
import { Codec } from './codec'
import { ArrayCodec, DecodedArray } from './array-codec'

Expand All @@ -41,7 +41,7 @@ const publicKeyHashCodec = new PublicKeyHashCodec()
const publicKeyHashesCodec = new ArrayCodec(publicKeyHashCodec)
const multiSigParser = Parser.start()
.nest('publicKeyHashes', { type: publicKeyHashesCodec.parser })
.nest('m', { type: compactUnsignedIntCodec.parser })
.nest('m', { type: compactSignedIntCodec.parser })
export interface MultiSig {
publicKeyHashes: DecodedArray<PublicKeyHash>
m: DecodedCompactInt
Expand Down Expand Up @@ -79,7 +79,7 @@ export class LockupScriptCodec implements Codec<LockupScript> {
result.push(...(input.script as PublicKeyHash).publicKeyHash)
} else if (input.scriptType === 1) {
result.push(...publicKeyHashesCodec.encode((input.script as MultiSig).publicKeyHashes.value))
result.push(...compactUnsignedIntCodec.encode((input.script as MultiSig).m))
result.push(...compactSignedIntCodec.encode((input.script as MultiSig).m))
} else if (input.scriptType === 2) {
result.push(...(input.script as P2SH).scriptHash)
} else if (input.scriptType === 3) {
Expand Down
2 changes: 1 addition & 1 deletion packages/web3/src/codec/transaction-codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { Either } from './either-codec'
import { AssetOutput, AssetOutputCodec } from './asset-output-codec'
import { ContractOutput, ContractOutputCodec } from './contract-output-codec'
import { FixedAssetOutput, Transaction as ApiTransaction } from '../api/api-alephium'
import { hexToBinUnsafe } from '..'
import { hexToBinUnsafe } from '../utils'
import { ContractOutput as ApiContractOutput } from '../api/api-alephium'
import { Codec } from './codec'
import { Output, outputCodec, outputsCodec } from './output-codec'
Expand Down
10 changes: 2 additions & 8 deletions packages/web3/src/contract/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import {
getDefaultPrimitiveValue,
PrimitiveTypes,
decodeArrayType,
fromApiPrimitiveVal
fromApiPrimitiveVal,
tryGetCallResult
} from '../api'
import { CompileProjectResult } from '../api/api-alephium'
import {
Expand Down Expand Up @@ -2369,10 +2370,3 @@ export const getContractIdFromUnsignedTx = async (

// This function only works in the simple case where a single non-subcontract is created in the tx
export const getTokenIdFromUnsignedTx = getContractIdFromUnsignedTx

export function tryGetCallResult(result: node.CallContractResult): node.CallContractSucceeded {
if (result.type === 'CallContractFailed') {
throw new Error(`Failed to call contract, error: ${(result as node.CallContractFailed).error}`)
}
return result as node.CallContractSucceeded
}
2 changes: 1 addition & 1 deletion packages/web3/src/contract/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You should have received a copy of the GNU Lesser General Public License
along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

import { web3 } from '..'
import * as web3 from '../global'
import { node } from '../api'
import { Subscription, SubscribeOptions } from '../utils'

Expand Down
2 changes: 1 addition & 1 deletion packages/web3/src/contract/ralph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
import * as ralph from './ralph'
import * as utils from '../utils'
import { Fields, FieldsSig, Struct, fromApiArray, fromApiEventFields, fromApiFields, getDefaultValue } from './contract'
import { node } from '..'
import * as node from '../api/api-alephium'

describe('contract', function () {
it('should encode I256', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/web3/src/signer/tx-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You should have received a copy of the GNU Lesser General Public License
along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

import { utils } from '..'
import { binToHex, contractIdFromAddress } from '../utils'
import { fromApiNumber256, node, NodeProvider, toApiNumber256Optional, toApiTokens } from '../api'
import { addressFromPublicKey } from '../utils'
import { toApiDestinations } from './signer'
Expand Down Expand Up @@ -90,7 +90,7 @@ export abstract class TransactionBuilder {
...rest
}
const response = await this.nodeProvider.contracts.postContractsUnsignedTxDeployContract(data)
const contractId = utils.binToHex(utils.contractIdFromAddress(response.contractAddress))
const contractId = binToHex(contractIdFromAddress(response.contractAddress))
return { ...response, groupIndex: response.fromGroup, contractId, gasPrice: fromApiNumber256(response.gasPrice) }
}

Expand Down
2 changes: 1 addition & 1 deletion packages/web3/src/transaction/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You should have received a copy of the GNU Lesser General Public License
along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

import { web3 } from '..'
import * as web3 from '../global'
import { node } from '../api'
import { Subscription, SubscribeOptions } from '../utils'

Expand Down
4 changes: 4 additions & 0 deletions packages/web3/src/utils/address.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ describe('address', function () {
expect(() => validateAddress('eBrjfQNeyUCuxE4zpbfMZcbS3PuvbMJDQBCyk4HRHtX')).toThrow('Invalid address:')
expect(validateAddress('yya86C6UemCeLs5Ztwjcf2Mp2Kkt4mwzzRpBiG6qQ9kj')).toBeUndefined()
expect(() => validateAddress('yya86C6UemCeLs5Ztwjcf2Mp2Kkt4mwzzRpBiG6qQ9k')).toThrow('Invalid address:')
expect(() => validateAddress('asd')).toThrow('Invalid multisig address: asd')
expect(() =>
validateAddress('2jVWAcAPphJ8ueZNG1BPwbfPFjjbvorprceuqzgmJQ1ZRyELRpWgARvdB3T9trqpiJs7f4GkudPt6rQLnGbQYqq2NCi')
).toThrow('Invalid multisig address, n: 2, m: 3')
})

it('should get address type', () => {
Expand Down
17 changes: 15 additions & 2 deletions packages/web3/src/utils/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import bs58 from './bs58'
import djb2 from './djb2'
import { binToHex, hexToBinUnsafe } from './utils'
import { KeyType } from '../signer'
import { MultiSig, lockupScriptCodec } from '../codec/lockup-script-codec'
import { Buffer } from 'buffer/'
import { compactSignedIntCodec } from '../codec'

const ec = new EC('secp256k1')

Expand All @@ -49,8 +52,18 @@ function decodeAndValidateAddress(address: string): Uint8Array {
if (decoded.length === 0) throw new Error('Address is empty')
const addressType = decoded[0]
if (addressType === AddressType.P2MPKH) {
// [1, n, ...hashes, m]
if ((decoded.length - 3) % 32 === 0) return decoded
let multisig: MultiSig
try {
multisig = lockupScriptCodec.decode(Buffer.from(decoded)).script as MultiSig
} catch (_) {
throw new Error(`Invalid multisig address: ${address}`)
}
const n = multisig.publicKeyHashes.value.length
const m = compactSignedIntCodec.toI32(multisig.m)
if (n < m) {
throw new Error(`Invalid multisig address, n: ${n}, m: ${m}`)
}
return decoded
} else if (addressType === AddressType.P2PKH || addressType === AddressType.P2SH || addressType === AddressType.P2C) {
// [type, ...hash]
if (decoded.length === 33) return decoded
Expand Down
11 changes: 11 additions & 0 deletions packages/web3/src/utils/exchange.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,26 @@ import { NodeProvider } from '../api'

describe('exchange', function () {
it('should get address from unlock script', () => {
expect(() =>
getAddressFromUnlockScript('0003498dc83e77e9b5c82b88e2bba7c737fd5aee041dc6bbb4402fefa3e7460a95bz')
).toThrow(`expected a hex string`)
expect(getAddressFromUnlockScript('0003498dc83e77e9b5c82b88e2bba7c737fd5aee041dc6bbb4402fefa3e7460a95bb')).toEqual(
'18Y5mtrpu9kaEW9PoyipNQcFwVtA8X5yrGYhTZwYBwXHN'
)
expect(() =>
getAddressFromUnlockScript('0003498dc83e77e9b5c82b88e2bba7c737fd5aee041dc6bbb4402fefa3e7460a95')
).toThrow('Invalid p2pkh unlock script')

expect(
getAddressFromUnlockScript(
'0201010000000004581440200000000000000000000000000000000000000000000000000000000000000000868500'
)
).toEqual('qCG5ZXg3b7AuGDS4HoEAhzqhCc2yxMqBYjYimBj1QFFT')
expect(() =>
getAddressFromUnlockScript(
'02010100000000045814402000000000000000000000000000000000000000000000000000000000000000008685'
)
).toThrow('Invalid p2sh unlock script')

expect(() =>
getAddressFromUnlockScript(
Expand Down
42 changes: 32 additions & 10 deletions packages/web3/src/utils/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,20 @@ You should have received a copy of the GNU Lesser General Public License
along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

import { AddressType, addressFromPublicKey, addressFromScript, binToHex, bs58, hexToBinUnsafe } from '..'
import {
AddressType,
addressFromPublicKey,
addressFromScript,
binToHex,
bs58,
hexToBinUnsafe,
isHexString
} from '../utils'
import { Transaction } from '../api/api-alephium'
import { Address } from '../signer'
import { P2SH, unlockScriptCodec } from '../codec/unlock-script-codec'
import { Buffer } from 'buffer/'
import { scriptCodec } from '../codec/script-codec'

export function validateExchangeAddress(address: string) {
let decoded: Uint8Array
Expand Down Expand Up @@ -80,24 +91,35 @@ enum UnlockScriptType {
}

export function getAddressFromUnlockScript(unlockScript: string): Address {
if (!isHexString(unlockScript)) {
throw new Error(`Invalid unlock script ${unlockScript}, expected a hex string`)
}
const decoded = hexToBinUnsafe(unlockScript)
if (decoded.length === 0) throw new Error('UnlockScript is empty')
const unlockScriptType = decoded[0]
const unlockScriptBody = decoded.slice(1)

if (unlockScriptType === UnlockScriptType.P2PKH) {
if (unlockScriptBody.length !== 33) {
throw new Error(`Invalid p2pkh unlock script: ${unlockScript}`)
}
return addressFromPublicKey(binToHex(unlockScriptBody))
} else if (unlockScriptType === UnlockScriptType.P2MPKH) {
}

if (unlockScriptType === UnlockScriptType.P2MPKH) {
throw new Error('Naive multi-sig address is not supported for exchanges as it will be replaced by P2SH')
} else if (unlockScriptType === UnlockScriptType.P2SH) {
// FIXEME: for now we assume that the params is empty, so we need to
// remove the last byte from the `unlockScriptBody`, we can decode
// the unlock script once the codec PR is merged
const script = unlockScriptBody.slice(0, -1)
return addressFromScript(script)
} else {
throw new Error('Invalid unlock script type')
}

if (unlockScriptType === UnlockScriptType.P2SH) {
let p2sh: P2SH
try {
p2sh = unlockScriptCodec.decode(Buffer.from(decoded)).script as P2SH
} catch (_) {
throw new Error(`Invalid p2sh unlock script: ${unlockScript}`)
}
return addressFromScript(scriptCodec.encode(p2sh.script))
}
throw new Error('Invalid unlock script type')
}

function checkALPHOutput(tx: Transaction): boolean {
Expand Down
2 changes: 1 addition & 1 deletion packages/web3/src/utils/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
// 2. https://github.com/ethers-io/ethers.js/blob/724881f34d428406488a1c9f9dbebe54b6edecda/src.ts/utils/fixednumber.ts

import BigNumber from 'bignumber.js'
import { Number256 } from '..'
import { Number256 } from '../api/types'

export const isNumeric = (numToCheck: any): boolean => !isNaN(parseFloat(numToCheck)) && isFinite(numToCheck)

Expand Down
2 changes: 1 addition & 1 deletion packages/web3/src/utils/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

import { ec as EC } from 'elliptic'
import { binToHex, encodeSignature, hexToBinUnsafe, signatureDecode } from '..'
import { binToHex, encodeSignature, hexToBinUnsafe, signatureDecode } from '../utils'
import { KeyType } from '../signer'
import * as necc from '@noble/secp256k1'
import { createHash, createHmac } from 'crypto'
Expand Down
Loading