diff --git a/packages/ckb-sdk-utils/README.md b/packages/ckb-sdk-utils/README.md index ab6b1bd7..1f467a5c 100644 --- a/packages/ckb-sdk-utils/README.md +++ b/packages/ckb-sdk-utils/README.md @@ -13,10 +13,10 @@ See [Full Doc](https://github.com/nervosnetwork/ckb-sdk-js/blob/develop/README.m - `utils.privateKeyToAddress`: get address from private key - `utils.pubkeyToAddress`: get address from public key - `utils.bech32Address`: args to short/full version address - - `utils.fullPayloadToAddress`: script to full version address + - `utils.fullPayloadToAddress`: script to full version address of obselete version, **deprecated and use `utils.scriptToAddress` instead** - `utils.parseAddress`: get address payload - `utils.addressToScript`: get lock script from address - - `utils.scriptToAddress`: get full address from script + - `utils.scriptToAddress`: get full address of new version from script - [Utils](#utils) @@ -151,15 +151,15 @@ utils.addressToScript('ckb1qsvf96jqmq4483ncl7yrzfzshwchu9jd0glq4yy5r2jcsw04d7xly ```js /** - * @description generate full version address from script, the address conforms to format type 0x00 + * @description generate full address of new version from script, the address conforms to format type 0x00 * @tutorial https://github.com/nervosnetwork/rfcs/pull/239/ */ utils.scriptToAddress({ - "codeHash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", + "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hashType": "type", - "args":"0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" + "args":"0xb39bbc0b3673c7d36450bc14cfcdad2d559c6c64" }) -// ckb1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqgqza8m903wt5xp5wuxjnurydg2x0qksh280gxqzqg0yp5wq +// ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdnnw7qkdnnclfkg59uzn8umtfd2kwxceqxwquc4 ``` ### Utils diff --git a/packages/ckb-sdk-utils/__tests__/address/fixtures.json b/packages/ckb-sdk-utils/__tests__/address/fixtures.json index e725ca0a..80e6313a 100644 --- a/packages/ckb-sdk-utils/__tests__/address/fixtures.json +++ b/packages/ckb-sdk-utils/__tests__/address/fixtures.json @@ -4,12 +4,20 @@ "params": ["0x36c329ed630d6ce750712a477543672adab57f4c"], "expected": [1, 0, 54, 195, 41, 237, 99, 13, 108, 231, 80, 113, 42, 71, 117, 67, 103, 42, 218, 181, 127, 76] }, - "should throw an error when its a full version address identified the hash_type and vm_version but code hash doesn't start with 0x":{ - "params": ["0x36c329ed630d6ce750712a477543672adab57f4c", "0x00", "3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356"], + "should throw an error when its a full version address identifies the hash_type but code hash doesn't start with 0x": { + "params": [ + "0x36c329ed630d6ce750712a477543672adab57f4c", + "0x00", + "3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356" + ], "exception": "'3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356' is not a valid code hash" }, - "should throw an error when its a full version address identified the hash_type and vm_version but code hash has invalid length":{ - "params": ["0x36c329ed630d6ce750712a477543672adab57f4c", "0x00", "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c135"], + "should throw an error when its a full version address identifies the hash_type but code hash has invalid length": { + "params": [ + "0x36c329ed630d6ce750712a477543672adab57f4c", + "0x00", + "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c135" + ], "exception": "'0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c135' is not a valid code hash" } }, @@ -36,17 +44,6 @@ ], "expected": "ckt1qsvf96jqmq4483ncl7yrzfzshwchu9jd0glq4yy5r2jcsw04d7xlydkr98kkxrtvuag8z2j8w4pkw2k6k4l5c02auef" }, - "data1 hash type":{ - "params": [ - { - "args": "0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101", - "type": "0x00", - "prefix": "ckt", - "codeHash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356" - } - ], - "expected": "ckt1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqsqza8m903wt5xp5wuxjnurydg2x0qksh280gxqzqg8gw2zh" - }, "default type = 0x02 and default prefix = ckb": { "params": [ { @@ -131,8 +128,10 @@ "params": ["ckb1qsvf96jqmq4483ncl7yrzfzshwchu9jd0glq4yy5r2jcsw04d7xlydkr98kkxrtvuag8z2j8w4pkw2k6k4l5czfy37k"], "expected": [4, 24, 146, 234, 64, 216, 43, 83, 198, 120, 255, 136, 49, 36, 80, 187, 177, 126, 22, 77, 122, 62, 10, 144, 148, 26, 165, 136, 57, 245, 111, 141, 242, 54, 195, 41, 237, 99, 13, 108, 231, 80, 113, 42, 71, 117, 67, 103, 42, 218, 181, 127, 76 ] }, - "full version address identified the hash_type and vm_version": { - "params": ["ckt1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqgqza8m903wt5xp5wuxjnurydg2x0qksh280gxqzqgutrqyp"], + "full version address identifies the hash_type": { + "params": [ + "ckt1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqgqza8m903wt5xp5wuxjnurydg2x0qksh280gxqzqgutrqyp" + ], "expected": [0, 52, 25, 161, 192, 158, 178, 86, 127, 101, 82, 238, 122, 142, 207, 253, 100, 21, 92, 255, 224, 241, 121, 110, 110, 97, 236, 8, 141, 116, 12, 19, 86, 1, 0, 23, 79, 178, 190, 46, 93, 12, 26, 59, 134, 148, 248, 50, 53, 10, 51, 193, 104, 93, 71, 122, 12, 1, 1] }, "should throw an error when short version address has invalid payload size": { @@ -159,21 +158,15 @@ "params": ["ckb1qsqcjt4ypkpt20r83lugxyj9pwa30cty6737p2gfgx493qul2cgvrxhw"], "exception": "'ckb1qsqcjt4ypkpt20r83lugxyj9pwa30cty6737p2gfgx493qul2cgvrxhw' is not a valid full version address" }, - "should throw an error when full version address identified the hash_type and vm_version has invalid code hash": { + "should throw an error when full version address identifies the hash_type has invalid code hash": { "params": ["ckb1qqv6rsy7kft87e2jaeaganlavs24ellq79ukumnpasyg6aqvzdtqzukxep"], "exception": "'ckb1qqv6rsy7kft87e2jaeaganlavs24ellq79ukumnpasyg6aqvzdtqzukxep' is not a valid address" }, - "should throw an error when full version address identified the hash_type and vm_version has invalid hash type": { - "params": ["ckb1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqcqza8m903wt5xp5wuxjnurydg2x0qksh280gxqzqgaxsc2r"], + "should throw an error when full version address identifies the hash_type has invalid hash type": { + "params": [ + "ckb1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqcqza8m903wt5xp5wuxjnurydg2x0qksh280gxqzqgaxsc2r" + ], "exception": "'ckb1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqcqza8m903wt5xp5wuxjnurydg2x0qksh280gxqzqgaxsc2r' is not a valid address" - }, - "should throw an error when full version address identified the hash_type and vm_version has invalid args length": { - "params": ["ckb1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqghy4uyeq"], - "exception": "'ckb1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqghy4uyeq' is not a valid address" - }, - "should throw an error when full version address identified the hash_type and vm_version has args not matching args len": { - "params": ["ckb1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqgqz7etutjapsdrhp55lqer2z3nc95963m6psqszx2kx5s"], - "exception": "'ckb1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqgqz7etutjapsdrhp55lqer2z3nc95963m6psqszx2kx5s' is not a valid address" } }, "addressToScript": { @@ -225,43 +218,43 @@ "args": "0x36c329ed630d6ce750712a477543672adab57f4c" } }, - "full version address identified the hash_type and vm_version": { - "params": ["ckt1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqgqza8m903wt5xp5wuxjnurydg2x0qksh280gxqzqgutrqyp"], + "full version address identifies the hash_type": { + "params": ["ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdnnw7qkdnnclfkg59uzn8umtfd2kwxceqxwquc4"], "expected": { - "codeHash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", + "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hashType": "type", - "args":"0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" + "args": "0xb39bbc0b3673c7d36450bc14cfcdad2d559c6c64" } } }, "scriptToAddress": { - "full version mainnet address identified the hash_type and vm_version": { + "full version mainnet address identifies the hash_type": { "params": [ { - "codeHash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", + "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hashType": "type", - "args":"0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" + "args": "0xb39bbc0b3673c7d36450bc14cfcdad2d559c6c64" } ], - "expected": "ckb1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqgqza8m903wt5xp5wuxjnurydg2x0qksh280gxqzqg0yp5wq" + "expected": "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdnnw7qkdnnclfkg59uzn8umtfd2kwxceqxwquc4" }, - "full version testnet address identified the hash_type and vm_version": { + "full version testnet address identifies the hash_type": { "params": [ { - "codeHash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", + "codeHash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hashType": "type", - "args":"0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" + "args": "0xb39bbc0b3673c7d36450bc14cfcdad2d559c6c64" }, false ], - "expected": "ckt1qq6pngwqn6e9vlm92th84rk0l4jp2h8lurchjmnwv8kq3rt5psf4vqgqza8m903wt5xp5wuxjnurydg2x0qksh280gxqzqgutrqyp" + "expected": "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdnnw7qkdnnclfkg59uzn8umtfd2kwxceqgutnjd" }, "should throw an error when args doesn't start with 0x": { "params": [ { "codeHash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", "hashType": "type", - "args":"4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" + "args": "4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" } ], "exception": "Hex string 4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101 should start with 0x" @@ -271,7 +264,7 @@ { "codeHash": "3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", "hashType": "type", - "args":"0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" + "args": "0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" } ], "exception": "'3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356' is not a valid code hash" @@ -281,7 +274,7 @@ { "codeHash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c135", "hashType": "type", - "args":"0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" + "args": "0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" } ], "exception": "'0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c135' is not a valid code hash" @@ -291,7 +284,7 @@ { "codeHash": "0x3419a1c09eb2567f6552ee7a8ecffd64155cffe0f1796e6e61ec088d740c1356", "hashType": "type1", - "args":"0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" + "args": "0x4fb2be2e5d0c1a3b8694f832350a33c1685d477a0c0101" } ], "exception": "'type1' is not a valid hash type" diff --git a/packages/ckb-sdk-utils/__tests__/exceptions/fixtures.json b/packages/ckb-sdk-utils/__tests__/exceptions/fixtures.json index dc484bb9..09ca82af 100644 --- a/packages/ckb-sdk-utils/__tests__/exceptions/fixtures.json +++ b/packages/ckb-sdk-utils/__tests__/exceptions/fixtures.json @@ -62,13 +62,6 @@ "message": "'0x03' is not a valid hash type" } }, - "ArgsLenException": { - "params": [""], - "expected": { - "code": 104, - "message": "'' is not a valid args length" - } - }, "OutLenTooSmallException": { "params": [16, 32], "expected": { diff --git a/packages/ckb-sdk-utils/package.json b/packages/ckb-sdk-utils/package.json index a0271e6a..4d918565 100644 --- a/packages/ckb-sdk-utils/package.json +++ b/packages/ckb-sdk-utils/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@nervosnetwork/ckb-types": "0.43.0", + "bech32": "2.0.0", "elliptic": "6.5.4", "jsbi": "3.1.3", "tslib": "2.3.1" diff --git a/packages/ckb-sdk-utils/src/address/index.ts b/packages/ckb-sdk-utils/src/address/index.ts index e0964d75..a8d81f8b 100644 --- a/packages/ckb-sdk-utils/src/address/index.ts +++ b/packages/ckb-sdk-utils/src/address/index.ts @@ -1,4 +1,4 @@ -import { bech32, blake160 } from '..' +import { blake160, bech32, bech32m } from '..' import { SECP256K1_BLAKE160, SECP256K1_MULTISIG, @@ -12,9 +12,10 @@ import { AddressPayloadException, CodeHashException, HashTypeException, - ArgsLenException, } from '../exceptions' +const MAX_BECH32_LIMIT = 1023 + // TODO: deprecate outdated methods export enum AddressPrefix { @@ -23,22 +24,19 @@ export enum AddressPrefix { } export enum AddressType { - FullVersion = '0x00', // full version identified the hash_type and vm_version + FullVersion = '0x00', // full version identifies the hash_type HashIdx = '0x01', // short version for locks with popular codehash DataCodeHash = '0x02', // full version with hash type 'Data', deprecated TypeCodeHash = '0x04', // full version with hash type 'Type', deprecated } +/** + * @description payload to a full address of new version + */ const payloadToAddress = (payload: Uint8Array, isMainnet = true) => - bech32.encode(isMainnet ? AddressPrefix.Mainnet : AddressPrefix.Testnet, bech32.toWords(payload)) + bech32m.encode(isMainnet ? AddressPrefix.Mainnet : AddressPrefix.Testnet, bech32.toWords(payload), MAX_BECH32_LIMIT) const scriptToPayload = ({ codeHash, hashType, args }: CKBComponents.Script): Uint8Array => { - enum HashType { - data = '00', - type = '01', - data1 = '02', - } - if (!args.startsWith('0x')) { throw new HexStringWithout0xException(args) } @@ -47,16 +45,26 @@ const scriptToPayload = ({ codeHash, hashType, args }: CKBComponents.Script): Ui throw new CodeHashException(codeHash) } + enum HashType { + data = '00', + type = '01', + data1 = '02', + } + if (!HashType[hashType]) { throw new HashTypeException(hashType) } - const argsLen = args.length / 2 - 1 - return hexToBytes( - `0x00${codeHash.slice(2)}${HashType[hashType]}${argsLen.toString(16).padStart(4, '0')}${args.slice(2)}`, - ) + return hexToBytes(`0x00${codeHash.slice(2)}${HashType[hashType]}${args.slice(2)}`) } +/** + * @function scriptToAddress + * @description The only way recommended to generated a full address of new version + * @param {object} script + * @param {booealn} isMainnet + * @returns {string} address + */ export const scriptToAddress = (script: CKBComponents.Script, isMainnet = true) => payloadToAddress(scriptToPayload(script), isMainnet) @@ -117,7 +125,7 @@ export const toAddressPayload = ( } /** - * @name bech32Address + * @function bech32Address * @description generate the address by bech32 algorithm * @param args, used as the identifier of an address, usually the public key hash is used. * @param {[string]} prefix, the prefix precedes the address, default to be ckb. @@ -128,11 +136,12 @@ export const toAddressPayload = ( export const bech32Address = ( args: Uint8Array | string, { prefix = AddressPrefix.Mainnet, type = AddressType.HashIdx, codeHashOrCodeHashIndex = '0x00' }: AddressOptions = {}, -) => bech32.encode(prefix, bech32.toWords(toAddressPayload(args, type, codeHashOrCodeHashIndex))) +) => bech32.encode(prefix, bech32.toWords(toAddressPayload(args, type, codeHashOrCodeHashIndex)), MAX_BECH32_LIMIT) /** + * @deprecated * @name fullPayloadToAddress - * @description generate the address with payload in full version format. + * @description deprecated method to generate the address with payload in full version format. Use scriptToAddress instead. * @param {string} args, used as the identifier of an address. * @param {[string]} prefix, the prefix precedes the address, default to be ckb. * @param {[string]} type, used to indicate which format the address conforms to, default to be 0x02, @@ -209,15 +218,6 @@ const isPayloadValid = (payload: Uint8Array) => { throw new HashTypeException(`0x${hashType.toString(16)}`) } - const argsLen = data.slice(33, 35) - if (argsLen.length < 2) { - throw new ArgsLenException(bytesToHex(argsLen)) - } - - if (data.slice(35).length !== +bytesToHex(argsLen)) { - throw new ArgsLenException(bytesToHex(argsLen)) - } - break } default: { @@ -238,8 +238,14 @@ export declare interface ParseAddress { * e.g. 0x | 01 | 00 | e2fa82e70b062c8644b80ad7ecf6e015e5f352f6 */ export const parseAddress: ParseAddress = (address: string, encode: 'binary' | 'hex' = 'binary'): any => { - const decoded = bech32.decode(address) - const payload = bech32.fromWords(new Uint8Array(decoded.words)) + let payload: Uint8Array = new Uint8Array() + try { + const decoded = bech32.decode(address, MAX_BECH32_LIMIT) + payload = new Uint8Array(bech32.fromWords(new Uint8Array(decoded.words))) + } catch { + const decoded = bech32m.decode(address, MAX_BECH32_LIMIT) + payload = new Uint8Array(bech32m.fromWords(new Uint8Array(decoded.words))) + } try { isPayloadValid(payload) } catch (err) { @@ -262,8 +268,7 @@ export const addressToScript = (address: string): CKBComponents.Script => { const codeHash = `0x${p.substr(4, 64)}` const hashType = HASH_TYPE[p.substr(68, 2)] - const argLen = parseInt(p.substr(70, 4), 16) - const args = `0x${p.substr(74, argLen * 2)}` + const args = `0x${p.substr(70)}` return { codeHash, hashType, args } } diff --git a/packages/ckb-sdk-utils/src/crypto/index.ts b/packages/ckb-sdk-utils/src/crypto/index.ts index 40a6d188..784cbc4f 100644 --- a/packages/ckb-sdk-utils/src/crypto/index.ts +++ b/packages/ckb-sdk-utils/src/crypto/index.ts @@ -1,15 +1,17 @@ +import { bech32, bech32m } from 'bech32' import blake2b from './blake2b' -import bech32 from './bech32' import blake160 from './blake160' module.exports = { blake2b, blake160, bech32, + bech32m, } export default { blake2b, blake160, bech32, + bech32m, } diff --git a/packages/ckb-sdk-utils/src/exceptions/address.ts b/packages/ckb-sdk-utils/src/exceptions/address.ts index 4b838fba..4b93b770 100644 --- a/packages/ckb-sdk-utils/src/exceptions/address.ts +++ b/packages/ckb-sdk-utils/src/exceptions/address.ts @@ -39,18 +39,9 @@ export class HashTypeException extends Error { } } -export class ArgsLenException extends Error { - code = ErrorCode.AddressInvalid - - constructor(argsLen: string) { - super(`'${argsLen}' is not a valid args length`) - } -} - export default { AddressPayloadException, AddressException, CodeHashException, HashTypeException, - ArgsLenException, } diff --git a/packages/ckb-sdk-utils/src/index.ts b/packages/ckb-sdk-utils/src/index.ts index d444b7f8..c8843628 100644 --- a/packages/ckb-sdk-utils/src/index.ts +++ b/packages/ckb-sdk-utils/src/index.ts @@ -17,7 +17,7 @@ export * as systemScripts from './systemScripts' export * as reconcilers from './reconcilers' export { serializeScript, serializeRawTransaction, serializeTransaction, serializeWitnessArgs, JSBI, PERSONAL } -export const { blake2b, bech32, blake160 } = crypto +export const { blake2b, bech32, bech32m, blake160 } = crypto export const scriptToHash = (script: CKBComponents.Script) => { if (!script) throw new ParameterRequiredException('Script') diff --git a/yarn.lock b/yarn.lock index 39dc9e49..94cd22a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2045,6 +2045,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +bech32@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" + integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== + bech32@^1.1.2: version "1.1.4" resolved "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9"