Merge pull request #323 from bitcoinjs/netcompat
HDNode: add optional network flag for importing
This commit is contained in:
commit
42f2a34b8a
5 changed files with 254 additions and 159 deletions
|
@ -11,21 +11,18 @@ var ECPubKey = require('./ecpubkey')
|
|||
var ecurve = require('ecurve')
|
||||
var curve = ecurve.getCurveByName('secp256k1')
|
||||
|
||||
function findBIP32ParamsByVersion(version) {
|
||||
function findBIP32NetworkByVersion(version) {
|
||||
for (var name in networks) {
|
||||
var network = networks[name]
|
||||
|
||||
if (version === network.bip32.private ||
|
||||
version === network.bip32.public) {
|
||||
|
||||
return {
|
||||
isPrivate: (version === network.bip32.private),
|
||||
network: network
|
||||
}
|
||||
return network
|
||||
}
|
||||
}
|
||||
|
||||
assert(false, 'Could not find version ' + version.toString(16))
|
||||
assert(false, 'Could not find network for ' + version.toString(16))
|
||||
}
|
||||
|
||||
function HDNode(K, chainCode, network) {
|
||||
|
@ -74,12 +71,12 @@ HDNode.fromSeedHex = function(hex, network) {
|
|||
return HDNode.fromSeedBuffer(new Buffer(hex, 'hex'), network)
|
||||
}
|
||||
|
||||
HDNode.fromBase58 = function(string) {
|
||||
return HDNode.fromBuffer(base58check.decode(string), true)
|
||||
HDNode.fromBase58 = function(string, network) {
|
||||
return HDNode.fromBuffer(base58check.decode(string), network, true)
|
||||
}
|
||||
|
||||
// FIXME: remove in 2.x.y
|
||||
HDNode.fromBuffer = function(buffer, __ignoreDeprecation) {
|
||||
HDNode.fromBuffer = function(buffer, network, __ignoreDeprecation) {
|
||||
if (!__ignoreDeprecation) {
|
||||
console.warn('HDNode.fromBuffer() is deprecated for removal in 2.x.y, use fromBase58 instead')
|
||||
}
|
||||
|
@ -88,7 +85,14 @@ HDNode.fromBuffer = function(buffer, __ignoreDeprecation) {
|
|||
|
||||
// 4 byte: version bytes
|
||||
var version = buffer.readUInt32BE(0)
|
||||
var params = findBIP32ParamsByVersion(version)
|
||||
|
||||
if (network) {
|
||||
assert(version === network.bip32.private || version === network.bip32.public, 'Network doesn\'t match')
|
||||
|
||||
// auto-detect
|
||||
} else {
|
||||
network = findBIP32NetworkByVersion(version)
|
||||
}
|
||||
|
||||
// 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ...
|
||||
var depth = buffer.readUInt8(4)
|
||||
|
@ -109,11 +113,11 @@ HDNode.fromBuffer = function(buffer, __ignoreDeprecation) {
|
|||
var data, hd
|
||||
|
||||
// 33 bytes: private key data (0x00 + k)
|
||||
if (params.isPrivate) {
|
||||
if (version === network.bip32.private) {
|
||||
assert.strictEqual(buffer.readUInt8(45), 0x00, 'Invalid private key')
|
||||
data = buffer.slice(46, 78)
|
||||
var d = BigInteger.fromBuffer(data)
|
||||
hd = new HDNode(d, chainCode, params.network)
|
||||
hd = new HDNode(d, chainCode, network)
|
||||
|
||||
// 33 bytes: public key data (0x02 + X or 0x03 + X)
|
||||
} else {
|
||||
|
@ -125,7 +129,7 @@ HDNode.fromBuffer = function(buffer, __ignoreDeprecation) {
|
|||
// If not, the extended public key is invalid.
|
||||
curve.validate(Q)
|
||||
|
||||
hd = new HDNode(Q, chainCode, params.network)
|
||||
hd = new HDNode(Q, chainCode, network)
|
||||
}
|
||||
|
||||
hd.depth = depth
|
||||
|
@ -136,8 +140,8 @@ HDNode.fromBuffer = function(buffer, __ignoreDeprecation) {
|
|||
}
|
||||
|
||||
// FIXME: remove in 2.x.y
|
||||
HDNode.fromHex = function(hex) {
|
||||
return HDNode.fromBuffer(new Buffer(hex, 'hex'))
|
||||
HDNode.fromHex = function(hex, network) {
|
||||
return HDNode.fromBuffer(new Buffer(hex, 'hex'), network)
|
||||
}
|
||||
|
||||
HDNode.prototype.getIdentifier = function() {
|
||||
|
|
74
test/fixtures/hdnode.json
vendored
74
test/fixtures/hdnode.json
vendored
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"valid": [
|
||||
{
|
||||
"network": "bitcoin",
|
||||
"master": {
|
||||
"seed": "000102030405060708090a0b0c0d0e0f",
|
||||
"wif": "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW",
|
||||
|
@ -8,7 +9,6 @@
|
|||
"chainCode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508",
|
||||
"hex": "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2",
|
||||
"hexPriv": "0488ade4000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
|
||||
"base58Priv": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
|
||||
"identifier": "3442193e1bb70916e914552172cd4e2dbc9df811",
|
||||
|
@ -23,9 +23,6 @@
|
|||
"wif": "L5BmPijJjrKbiUfG4zbiFKNqkvuJ8usooJmzuD7Z8dkRoTThYnAT",
|
||||
"pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56",
|
||||
"chainCode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141",
|
||||
"hex": "0488b21e013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56",
|
||||
"hexPriv": "0488ade4013442193e8000000047fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae623614100edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw",
|
||||
"base58Priv": "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
|
||||
"identifier": "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7",
|
||||
|
@ -38,9 +35,6 @@
|
|||
"wif": "KyFAjQ5rgrKvhXvNMtFB5PCSKUYD1yyPEe3xr3T34TZSUHycXtMM",
|
||||
"pubKey": "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c",
|
||||
"chainCode": "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19",
|
||||
"hex": "0488b21e025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c1903501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c",
|
||||
"hexPriv": "0488ade4025c1bd648000000012a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19003c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ",
|
||||
"base58Priv": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
|
||||
"identifier": "bef5a2f9a56a94aab12459f72ad9cf8cf19c7bbe",
|
||||
|
@ -54,9 +48,6 @@
|
|||
"wif": "L43t3od1Gh7Lj55Bzjj1xDAgJDcL7YFo2nEcNaMGiyRZS1CidBVU",
|
||||
"pubKey": "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2",
|
||||
"chainCode": "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f",
|
||||
"hex": "0488b21e03bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2",
|
||||
"hexPriv": "0488ade403bef5a2f98000000204466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f00cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5",
|
||||
"base58Priv": "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
|
||||
"identifier": "ee7ab90cde56a8c0e2bb086ac49748b8db9dce72",
|
||||
|
@ -69,9 +60,6 @@
|
|||
"wif": "KwjQsVuMjbCP2Zmr3VaFaStav7NvevwjvvkqrWd5Qmh1XVnCteBR",
|
||||
"pubKey": "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29",
|
||||
"chainCode": "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd",
|
||||
"hex": "0488b21e04ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29",
|
||||
"hexPriv": "0488ade404ee7ab90c00000002cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd000f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV",
|
||||
"base58Priv": "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
|
||||
"identifier": "d880d7d893848509a62d8fb74e32148dac68412f",
|
||||
|
@ -84,9 +72,6 @@
|
|||
"wif": "Kybw8izYevo5xMh1TK7aUr7jHFCxXS1zv8p3oqFz3o2zFbhRXHYs",
|
||||
"pubKey": "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011",
|
||||
"chainCode": "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e",
|
||||
"hex": "0488b21e05d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011",
|
||||
"hexPriv": "0488ade405d880d7d83b9aca00c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e00471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy",
|
||||
"base58Priv": "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
|
||||
"identifier": "d69aa102255fed74378278c7812701ea641fdf32",
|
||||
|
@ -96,12 +81,12 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"network": "bitcoin",
|
||||
"master": {
|
||||
"seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
||||
"wif": "KyjXhyHF9wTphBkfpxjL8hkDXDUSbE3tKANT94kXSyh6vn6nKaoy",
|
||||
"pubKey": "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7",
|
||||
"chainCode": "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
|
||||
"base58Priv": "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
|
||||
"hex": "0488b21e00000000000000000060499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd968903cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7",
|
||||
|
@ -117,9 +102,6 @@
|
|||
"wif": "L2ysLrR6KMSAtx7uPqmYpoTeiRzydXBattRXjXz5GDFPrdfPzKbj",
|
||||
"pubKey": "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea",
|
||||
"chainCode": "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c",
|
||||
"hex": "0488b21e01bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea",
|
||||
"hexPriv": "0488ade401bd16bee500000000f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c00abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH",
|
||||
"base58Priv": "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
|
||||
"identifier": "5a61ff8eb7aaca3010db97ebda76121610b78096",
|
||||
|
@ -133,9 +115,6 @@
|
|||
"wif": "L1m5VpbXmMp57P3knskwhoMTLdhAAaXiHvnGLMribbfwzVRpz2Sr",
|
||||
"pubKey": "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b",
|
||||
"chainCode": "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9",
|
||||
"hex": "0488b21e025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d903c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b",
|
||||
"hexPriv": "0488ade4025a61ff8effffffffbe17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d900877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a",
|
||||
"base58Priv": "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
|
||||
"identifier": "d8ab493736da02f11ed682f88339e720fb0379d1",
|
||||
|
@ -148,9 +127,6 @@
|
|||
"wif": "KzyzXnznxSv249b4KuNkBwowaN3akiNeEHy5FWoPCJpStZbEKXN2",
|
||||
"pubKey": "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9",
|
||||
"chainCode": "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb",
|
||||
"hex": "0488b21e03d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9",
|
||||
"hexPriv": "0488ade403d8ab493700000001f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb00704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon",
|
||||
"base58Priv": "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
|
||||
"identifier": "78412e3a2296a40de124307b6485bd19833e2e34",
|
||||
|
@ -164,9 +140,6 @@
|
|||
"wif": "L5KhaMvPYRW1ZoFmRjUtxxPypQ94m6BcDrPhqArhggdaTbbAFJEF",
|
||||
"pubKey": "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0",
|
||||
"chainCode": "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29",
|
||||
"hex": "0488b21e0478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2902d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0",
|
||||
"hexPriv": "0488ade40478412e3afffffffe637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e2900f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",
|
||||
"base58Priv": "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
|
||||
"identifier": "31a507b815593dfc51ffc7245ae7e5aee304246e",
|
||||
|
@ -179,9 +152,6 @@
|
|||
"wif": "L3WAYNAZPxx1fr7KCz7GN9nD5qMBnNiqEJNJMU1z9MMaannAt4aK",
|
||||
"pubKey": "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c",
|
||||
"chainCode": "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271",
|
||||
"hex": "0488b21e0531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c",
|
||||
"hexPriv": "0488ade40531a507b8000000029452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed27100bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23",
|
||||
"network": "bitcoin",
|
||||
"base58": "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt",
|
||||
"base58Priv": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
|
||||
"identifier": "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220",
|
||||
|
@ -189,6 +159,37 @@
|
|||
"address": "14UKfRV9ZPUp6ZC9PLhqbRtxdihW9em3xt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"network": "litecoin",
|
||||
"master": {
|
||||
"seed": "000102030405060708090a0b0c0d0e0f",
|
||||
"wif": "TAroS5Knm8GZcnpPycBgzjwwDLWMyQjDrcuGPPoArgrbW7Ln22qp",
|
||||
"pubKey": "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2",
|
||||
"chainCode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508",
|
||||
"hex": "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2",
|
||||
"hexPriv": "019d9cfe000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
|
||||
"base58": "Ltub2SSUS19CirucWFod2ZsYA2J4v4U76YiCXHdcQttnoiy5aGanFHCPDBX7utfG6f95u1cUbZJNafmvzNCzZZJTw1EmyFoL8u1gJbGM8ipu491",
|
||||
"base58Priv": "Ltpv71G8qDifUiNetP6nmxPA5STrUVmv2J9YSmXajv8VsYBUyuPhvN9xCaQrfX2wo5xxJNtEazYCFRUu5FmokYMM79pcqz8pcdo4rNXAFPgyB4k",
|
||||
"identifier": "3442193e1bb70916e914552172cd4e2dbc9df811",
|
||||
"fingerprint": "3442193e",
|
||||
"address": "LPzGaoLUtXFkmNo3u1chDxGxDnSaBQTTxm"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"description": "m/0'",
|
||||
"m": 0,
|
||||
"hardened": true,
|
||||
"wif": "TB22qU2V9EJCVKJ8cdYaTfvDhnYcCzthcWgFm1k6hbvbKM1NLxoL",
|
||||
"pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56",
|
||||
"chainCode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141",
|
||||
"base58": "Ltub2UhtRiSfp82berwLEKkB34QBEt2TUdCDCu4WNzGumvAMwYsxfWjULKsXhADxqy3cuDu3TnqoKJr1xmB8Wb2qzthWAtbb4CutpXPuSU1YMgG",
|
||||
"base58Priv": "Ltpv73XYpw28ZyVe2zEVyiFnxUZxoKLGQNdZ8NxUi1WcqjNmMBgtLbh3KimGSnPHCoLv1RmvxHs4dnKmo1oXQ8dXuDu8uroxrbVxZPA1gXboYvx",
|
||||
"identifier": "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7",
|
||||
"fingerprint": "5c1bd648",
|
||||
"address": "LTcyn1jun6g9hvxtsT7cqMRSyix7AULC76"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"invalid": {
|
||||
|
@ -198,8 +199,13 @@
|
|||
"string": "xprvQQQQQQQQQQQQQQQQCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334"
|
||||
},
|
||||
{
|
||||
"exception": "Could not find version 0",
|
||||
"exception": "Could not find network for 0",
|
||||
"string": "1111111111111adADjFaSNPxwXqLjHLj4mBfYxuewDPbw9hEj1uaXCzMxRPXDFF3cUoezTFYom4sEmEVSQmENPPR315cFk9YUFVek73wE9"
|
||||
},
|
||||
{
|
||||
"exception": "Network doesn\\'t match",
|
||||
"string": "Ltpv73XYpw28ZyVe2zEVyiFnxUZxoKLGQNdZ8NxUi1WcqjNmMBgtLbh3KimGSnPHCoLv1RmvxHs4dnKmo1oXQ8dXuDu8uroxrbVxZPA1gXboYvx",
|
||||
"network": "bitcoin"
|
||||
}
|
||||
],
|
||||
"fromBuffer": [
|
||||
|
@ -220,7 +226,7 @@
|
|||
"hex": "0488b21e0000000000ffffffff7ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355"
|
||||
},
|
||||
{
|
||||
"exception": "Could not find version 22222222",
|
||||
"exception": "Could not find network for 22222222",
|
||||
"hex": "222222220000000000000000007ffc03d4a1f2fb41ef93374c69e4d19e42e27c9a87ec8b799a205eecd3b43b5f02948d03e260a571e21bcf5bfd8e3b6602800df154906e06b2bc88eee410aee355"
|
||||
},
|
||||
{
|
||||
|
|
223
test/fixtures/network.json
vendored
223
test/fixtures/network.json
vendored
|
@ -1,84 +1,151 @@
|
|||
{
|
||||
"valid": [
|
||||
{
|
||||
"description": "when txSize < 1kb",
|
||||
"network": "bitcoin",
|
||||
"txSize": 1,
|
||||
"fee": 10000
|
||||
},
|
||||
{
|
||||
"description": "when txSize >= 1kb",
|
||||
"network": "bitcoin",
|
||||
"txSize": 1000,
|
||||
"fee": 10000
|
||||
},
|
||||
{
|
||||
"description": "rounding",
|
||||
"network": "bitcoin",
|
||||
"txSize": 2800,
|
||||
"fee": 30000
|
||||
},
|
||||
{
|
||||
"description": "when outputs.value > DUST_SOFT_LIMIT, feePerKb is used",
|
||||
"network": "dogecoin",
|
||||
"txSize": 1000,
|
||||
"outputs": [
|
||||
{
|
||||
"value": 100000000
|
||||
"valid": {
|
||||
"constants": [
|
||||
{
|
||||
"network": "bitcoin",
|
||||
"bip32": {
|
||||
"private": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
|
||||
"public": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
|
||||
}
|
||||
],
|
||||
"fee": 100000000
|
||||
},
|
||||
{
|
||||
"description": "when not every outputs.value > DUST_SOFT_LIMIT",
|
||||
"network": "dogecoin",
|
||||
"txSize": 1000,
|
||||
"outputs": [
|
||||
{
|
||||
"value": 99999999
|
||||
},
|
||||
{
|
||||
"value": 99999999
|
||||
},
|
||||
{
|
||||
"network": "testnet",
|
||||
"bip32": {
|
||||
"private": "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m",
|
||||
"public": "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp"
|
||||
}
|
||||
],
|
||||
"fee": 300000000
|
||||
},
|
||||
{
|
||||
"description": "rounding",
|
||||
"network": "dogecoin",
|
||||
"txSize": 2800,
|
||||
"fee": 300000000
|
||||
},
|
||||
{
|
||||
"description": "when outputs.value > DUST_SOFT_LIMIT, feePerKb is used",
|
||||
"network": "litecoin",
|
||||
"txSize": 1000,
|
||||
"outputs": [
|
||||
{
|
||||
"value": 100000
|
||||
},
|
||||
{
|
||||
"network": "litecoin",
|
||||
"bip32": {
|
||||
"private": "Ltpv71G8qDifUiNetP6nmxPA5STrUVmv2J9YSmXajv8VsYBUyuPhvN9xCaQrfX2wo5xxJNtEazYCFRUu5FmokYMM79pcqz8pcdo4rNXAFPgyB4k",
|
||||
"public": "Ltub2SSUS19CirucWFod2ZsYA2J4v4U76YiCXHdcQttnoiy5aGanFHCPDBX7utfG6f95u1cUbZJNafmvzNCzZZJTw1EmyFoL8u1gJbGM8ipu491"
|
||||
}
|
||||
],
|
||||
"fee": 100000
|
||||
},
|
||||
{
|
||||
"description": "when not every outputs.value > DUST_SOFT_LIMIT",
|
||||
"network": "litecoin",
|
||||
"txSize": 1000,
|
||||
"outputs": [
|
||||
{
|
||||
"value": 99999
|
||||
},
|
||||
{
|
||||
"value": 99999
|
||||
},
|
||||
{
|
||||
"network": "dogecoin",
|
||||
"bip32": {
|
||||
"private": "dgpv51eADS3spNJh9Gjth94XcPwAczvQaDJs9rqx11kvxKs6r3Ek8AgERHhjLs6mzXQFHRzQqGwqdeoDkZmr8jQMBfi43b7sT3sx3cCSk5fGeUR",
|
||||
"public": "dgub8kXBZ7ymNWy2S8Q3jNgVjFUm5ZJ3QLLaSTdAA89ukSv7Q6MSXwE14b7Nv6eDpE9JJXinTKc8LeLVu19uDPrm5uJuhpKNzV2kAgncwo6bNpP"
|
||||
}
|
||||
],
|
||||
"fee": 300000
|
||||
},
|
||||
{
|
||||
"description": "rounding",
|
||||
"network": "litecoin",
|
||||
"txSize": 2800,
|
||||
"fee": 300000
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"network": "viacoin",
|
||||
"bip32": {
|
||||
"private": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
|
||||
"public": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"network": "viacointestnet",
|
||||
"bip32": {
|
||||
"private": "tprv8ZgxMBicQKsPeDgjzdC36fs6bMjGApWDNLR9erAXMs5skhMv36j9MV5ecvfavji5khqjWaWSFhN3YcCUUdiKH6isR4Pwy3U5y5egddBr16m",
|
||||
"public": "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"network": "gamerscoin",
|
||||
"bip32": {
|
||||
"private": "Ltpv71G8qDifUiNetP6nmxPA5STrUVmv2J9YSmXajv8VsYBUyuPhvN9xCaQrfX2wo5xxJNtEazYCFRUu5FmokYMM79pcqz8pcdo4rNXAFPgyB4k",
|
||||
"public": "Ltub2SSUS19CirucWFod2ZsYA2J4v4U76YiCXHdcQttnoiy5aGanFHCPDBX7utfG6f95u1cUbZJNafmvzNCzZZJTw1EmyFoL8u1gJbGM8ipu491"
|
||||
}
|
||||
},
|
||||
{
|
||||
"network": "jumbucks",
|
||||
"bip32": {
|
||||
"private": "jprv5eCacBgN4Bz4zYxgVQ7RDt1a3eREhEaj8KjAcJ7YwogxGo2rmBF5kvAQS53JwZpo5wnUmJ9Q7kB6b2gQ1MzC6yaTc188hr6hXZ5t8Ruria1",
|
||||
"public": "jpub1sBw1hDFtZYND339bReRb1xJbgFj6hJaVYemQgXAW9Dw9bN1JiZLJiUtHLgcTTEs1UgRGFAYm3XQPYsYJbpqj1aYPhrMsNcJHfgdAhvFZBB"
|
||||
}
|
||||
},
|
||||
{
|
||||
"network": "zetacoin",
|
||||
"bip32": {
|
||||
"private": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
|
||||
"public": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
|
||||
}
|
||||
}
|
||||
],
|
||||
"estimateFee": [
|
||||
{
|
||||
"description": "when txSize < 1kb",
|
||||
"network": "bitcoin",
|
||||
"txSize": 1,
|
||||
"fee": 10000
|
||||
},
|
||||
{
|
||||
"description": "when txSize >= 1kb",
|
||||
"network": "bitcoin",
|
||||
"txSize": 1000,
|
||||
"fee": 10000
|
||||
},
|
||||
{
|
||||
"description": "rounding",
|
||||
"network": "bitcoin",
|
||||
"txSize": 2800,
|
||||
"fee": 30000
|
||||
},
|
||||
{
|
||||
"description": "when outputs.value > DUST_SOFT_LIMIT, feePerKb is used",
|
||||
"network": "dogecoin",
|
||||
"txSize": 1000,
|
||||
"outputs": [
|
||||
{
|
||||
"value": 100000000
|
||||
}
|
||||
],
|
||||
"fee": 100000000
|
||||
},
|
||||
{
|
||||
"description": "when not every outputs.value > DUST_SOFT_LIMIT",
|
||||
"network": "dogecoin",
|
||||
"txSize": 1000,
|
||||
"outputs": [
|
||||
{
|
||||
"value": 99999999
|
||||
},
|
||||
{
|
||||
"value": 99999999
|
||||
}
|
||||
],
|
||||
"fee": 300000000
|
||||
},
|
||||
{
|
||||
"description": "rounding",
|
||||
"network": "dogecoin",
|
||||
"txSize": 2800,
|
||||
"fee": 300000000
|
||||
},
|
||||
{
|
||||
"description": "when outputs.value > DUST_SOFT_LIMIT, feePerKb is used",
|
||||
"network": "litecoin",
|
||||
"txSize": 1000,
|
||||
"outputs": [
|
||||
{
|
||||
"value": 100000
|
||||
}
|
||||
],
|
||||
"fee": 100000
|
||||
},
|
||||
{
|
||||
"description": "when not every outputs.value > DUST_SOFT_LIMIT",
|
||||
"network": "litecoin",
|
||||
"txSize": 1000,
|
||||
"outputs": [
|
||||
{
|
||||
"value": 99999
|
||||
},
|
||||
{
|
||||
"value": 99999
|
||||
}
|
||||
],
|
||||
"fee": 300000
|
||||
},
|
||||
{
|
||||
"description": "rounding",
|
||||
"network": "litecoin",
|
||||
"txSize": 2800,
|
||||
"fee": 300000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,9 +65,10 @@ describe('HDNode', function() {
|
|||
describe('fromSeed*', function() {
|
||||
fixtures.valid.forEach(function(f) {
|
||||
it('calculates privKey and chainCode for ' + f.master.fingerprint, function() {
|
||||
var hd = HDNode.fromSeedHex(f.master.seed)
|
||||
var network = networks[f.network]
|
||||
var hd = HDNode.fromSeedHex(f.master.seed, network)
|
||||
|
||||
assert.equal(hd.privKey.toWIF(), f.master.wif)
|
||||
assert.equal(hd.privKey.toWIF(network), f.master.wif)
|
||||
assert.equal(hd.chainCode.toString('hex'), f.master.chainCode)
|
||||
})
|
||||
})
|
||||
|
@ -88,7 +89,8 @@ describe('HDNode', function() {
|
|||
describe('toBase58', function() {
|
||||
fixtures.valid.forEach(function(f) {
|
||||
it('exports ' + f.master.base58 + ' (public) correctly', function() {
|
||||
var hd = HDNode.fromSeedHex(f.master.seed).neutered()
|
||||
var network = networks[f.network]
|
||||
var hd = HDNode.fromSeedHex(f.master.seed, network).neutered()
|
||||
|
||||
assert.equal(hd.toBase58(), f.master.base58)
|
||||
})
|
||||
|
@ -96,7 +98,8 @@ describe('HDNode', function() {
|
|||
|
||||
fixtures.valid.forEach(function(f) {
|
||||
it('exports ' + f.master.base58Priv + ' (private) correctly', function() {
|
||||
var hd = HDNode.fromSeedHex(f.master.seed)
|
||||
var network = networks[f.network]
|
||||
var hd = HDNode.fromSeedHex(f.master.seed, network)
|
||||
|
||||
assert.equal(hd.toBase58(), f.master.base58Priv)
|
||||
})
|
||||
|
@ -132,7 +135,9 @@ describe('HDNode', function() {
|
|||
fixtures.invalid.fromBase58.forEach(function(f) {
|
||||
it('throws on ' + f.string, function() {
|
||||
assert.throws(function() {
|
||||
HDNode.fromBase58(f.string)
|
||||
var network = networks[f.network]
|
||||
|
||||
HDNode.fromBase58(f.string, network)
|
||||
}, new RegExp(f.exception))
|
||||
})
|
||||
})
|
||||
|
@ -175,7 +180,8 @@ describe('HDNode', function() {
|
|||
|
||||
fixtures.valid.forEach(function(f) {
|
||||
it('exports ' + f.master.hexPriv + ' (private) correctly', function() {
|
||||
var hd = HDNode.fromSeedHex(f.master.seed)
|
||||
var network = networks[f.network]
|
||||
var hd = HDNode.fromSeedHex(f.master.seed, network)
|
||||
|
||||
assert.equal(hd.toHex(), f.master.hexPriv)
|
||||
})
|
||||
|
@ -212,19 +218,12 @@ describe('HDNode', function() {
|
|||
})
|
||||
|
||||
describe('getAddress', function() {
|
||||
var f = fixtures.valid[0]
|
||||
fixtures.valid.forEach(function(f) {
|
||||
it('returns ' + f.master.address + ' for ' + f.master.fingerprint, function() {
|
||||
var hd = HDNode.fromBase58(f.master.base58)
|
||||
|
||||
it('returns the Address (pubHash) for ' + f.master.fingerprint, function() {
|
||||
var hd = HDNode.fromBase58(f.master.base58)
|
||||
|
||||
assert.equal(hd.getAddress().toString(), f.master.address)
|
||||
})
|
||||
|
||||
it('supports alternative networks', function() {
|
||||
var hd = HDNode.fromBase58(f.master.base58)
|
||||
hd.network = networks.testnet
|
||||
|
||||
assert.equal(hd.getAddress().version, networks.testnet.pubKeyHash)
|
||||
assert.equal(hd.getAddress().toString(), f.master.address)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -244,8 +243,8 @@ describe('HDNode', function() {
|
|||
})
|
||||
|
||||
describe('derive', function() {
|
||||
function verifyVector(hd, v, depth) {
|
||||
assert.equal(hd.privKey.toWIF(), v.wif)
|
||||
function verifyVector(hd, network, v, depth) {
|
||||
assert.equal(hd.privKey.toWIF(network), v.wif)
|
||||
assert.equal(hd.pubKey.toHex(), v.pubKey)
|
||||
assert.equal(hd.chainCode.toString('hex'), v.chainCode)
|
||||
assert.equal(hd.depth, depth || 0)
|
||||
|
@ -257,8 +256,9 @@ describe('HDNode', function() {
|
|||
}
|
||||
}
|
||||
|
||||
fixtures.valid.forEach(function(f, j) {
|
||||
var hd = HDNode.fromSeedHex(f.master.seed)
|
||||
fixtures.valid.forEach(function(f) {
|
||||
var network = networks[f.network]
|
||||
var hd = HDNode.fromSeedHex(f.master.seed, network)
|
||||
|
||||
// FIXME: test data is only testing Private -> private for now
|
||||
f.children.forEach(function(c, i) {
|
||||
|
@ -270,7 +270,7 @@ describe('HDNode', function() {
|
|||
hd = hd.derive(c.m)
|
||||
}
|
||||
|
||||
verifyVector(hd, c, i + 1)
|
||||
verifyVector(hd, network, c, i + 1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
var assert = require('assert')
|
||||
var networks = require('../src/networks')
|
||||
var sinon = require('sinon')
|
||||
|
||||
var HDNode = require('../src/hdnode')
|
||||
var Transaction = require('../src/transaction')
|
||||
|
||||
var fixtures = require('./fixtures/network')
|
||||
|
@ -15,19 +17,35 @@ describe('networks', function() {
|
|||
Transaction.prototype.toBuffer.restore()
|
||||
})
|
||||
|
||||
fixtures.valid.forEach(function(f) {
|
||||
describe(f.network + ' estimateFee', function() {
|
||||
describe('constants', function() {
|
||||
fixtures.valid.constants.forEach(function(f) {
|
||||
var network = networks[f.network]
|
||||
|
||||
it('calculates the fee correctly for ' + f.description, function() {
|
||||
var buffer = new Buffer(f.txSize)
|
||||
txToBuffer.returns(buffer)
|
||||
Object.keys(f.bip32).forEach(function(name) {
|
||||
var extb58 = f.bip32[name]
|
||||
|
||||
var estimateFee = network.estimateFee
|
||||
var tx = new Transaction()
|
||||
tx.outs = f.outputs || []
|
||||
it('resolves ' + extb58 + ' to ' + f.network, function() {
|
||||
assert.equal(HDNode.fromBase58(extb58, network).network, network)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
assert.equal(estimateFee(tx), f.fee)
|
||||
describe('estimateFee', function() {
|
||||
fixtures.valid.estimateFee.forEach(function(f) {
|
||||
describe('(' + f.network + ')', function() {
|
||||
var network = networks[f.network]
|
||||
|
||||
it('calculates the fee correctly for ' + f.description, function() {
|
||||
var buffer = new Buffer(f.txSize)
|
||||
txToBuffer.returns(buffer)
|
||||
|
||||
var estimateFee = network.estimateFee
|
||||
var tx = new Transaction()
|
||||
tx.outs = f.outputs || []
|
||||
|
||||
assert.equal(estimateFee(tx), f.fee)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue