Skip to content

Commit

Permalink
Merge pull request #354 from alephium/fix-validate-address
Browse files Browse the repository at this point in the history
Fix validate address
  • Loading branch information
polarker authored May 23, 2024
2 parents 8c18457 + 7c99351 commit 65516e4
Show file tree
Hide file tree
Showing 16 changed files with 85 additions and 34 deletions.
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

0 comments on commit 65516e4

Please sign in to comment.