지갑 레벨 QR (bittool-signer-wallet v1)
지갑 레벨 QR 코드는 BitTool Signer 지갑 하나의 모든 계정을 한 번의 스캔으로 내보냅니다. 단일 서명 주소 유형별 Bitcoin 계정 4개, EVM 계정과 TRON 계정, 그리고 Solana 프리셋 주소 3개가 포함됩니다. 앱에서는 Connect › Wallet Level에 있습니다.
인코딩
pairing object (text keys, see below)
→ canonical CBOR (RFC 8949 §4.2.1) "inner", 1561 bytes for the test wallet
→ CBOR byte string containing those bytes "outer"
→ UR type "bytes" (BCR-2020-006)
→ multi-part UR, 400-byte fragments, upper-case 4 frames for the test wallet
중요한 세부 사항은 네 가지입니다.
- CBOR 계층이 두 개입니다. UR 본문은 CBOR 바이트 문자열이고, 페어링 맵은 그 안에 들어 있습니다. 바깥 계층을 디코딩해 바이트를 얻은 다음, 그 바이트를 다시 디코딩하세요. JSON도, tag-24 항목도,
ur:bittool-…같은 사용자 정의 타입도 아닙니다. - 텍스트 키를 사용합니다. 맵 키는 JSON 필드 이름을 CBOR 텍스트 문자열로 쓴 것이므로, CBOR과 아래 JSON 보기는 같은 데이터입니다.
- 정규(canonical) 인코딩입니다. 확정 길이, 가장 짧은 정수 형식을 사용하며, 맵 키는 인코딩된 바이트 기준으로 정렬합니다(텍스트 키라면 짧은 키가 먼저, 그다음 바이트 순). 텍스트, 부호 없는 정수, 배열, 맵만 나타나며, 값이 없는 필드는 생략합니다.
- 항상 애니메이션입니다. 프래그먼트 크기는 400바이트이고 페이로드의 절반을 넘지 않으므로, 프레임은 항상 두 개 이상입니다. BitTool Signer는 4,096바이트를 넘는 페이로드를 거부합니다. 스캐너는 디코딩하기 전에 모든 프레임을 (순서와 상관없이) 수집해야 합니다.
정규 키 순서 예시: 객체 {version: 1, mode: "full", b: "x", aa: 2}는 다음과 같이 인코딩됩니다.
a4 61 62 61 78 62 61 61 02 64 6d6f6465 64 66756c6c 67 76657273696f6e 01
"b" "x" "aa" 2 "mode" "full" "version" 1
페어링 객체
엔벨로프
| 필드 | 타입 | 값 |
|---|---|---|
protocol | text | "bittool-signer-wallet" |
version | uint | 1. 다른 버전은 모두 거부하고, 더 높은 버전을 v1로 읽어서는 안 됩니다. |
mode | text | "full". BitTool Signer는 full만 생성합니다. |
network | text | "mainnet" |
masterFingerprint | text | BIP32 마스터 키 지문, 소문자 16진수 8자리 |
accounts | array | 항목 7개(아래 설명 참고). 위치가 아니라 chain과 script/profile로 찾으세요. |
계정
chain | 필드 | 참고 |
|---|---|---|
btc (×4) | accountPath, script, xpub, derive: {receive: "0/*", change: "1/*"} | script마다 하나씩: p2wpkh (m/84'/0'/0'), p2tr (m/86'/0'/0'), p2sh-p2wpkh (m/49'/0'/0'), p2pkh (m/44'/0'/0') |
evm | accountPath m/44'/60'/0', xpub, derive: "0/*" | 모든 EVM 네트워크에 계정 하나 |
tron | accountPath m/44'/195'/0', xpub, derive: "0/*" | |
sol | profile: "bip44-solana", accounts: [{index, profile, path, address}] | 항목 3개: standard m/44'/501'/0'/0', trust m/44'/501'/0', legacy m/44'/501' |
모든 xpub은 SegWit과 Taproot에서도 표준 메인넷 버전 바이트 0x0488B21E를 사용합니다. 어떤 주소를 파생할지는 ypub/zpub 접두사가 아니라 script 필드가 알려 줍니다. 멀티시그(BIP48) 공동 서명자 키는 포함되지 않습니다. 멀티시그 지갑에는 모든 공동 서명자와 임계값이 필요하므로, 이 경우에는 코인 레벨 공동 서명자 내보내기를 사용하세요.
테스트 벡터
패스프레이즈 없이 공개 테스트 문구 abandon … about에서 생성했습니다. 스캐너로 애니메이션을 비추거나 아래 프레임을 사용하세요.
{
"protocol": "bittool-signer-wallet",
"version": 1,
"mode": "full",
"network": "mainnet",
"masterFingerprint": "73c5da0a",
"accounts": [
{
"chain": "btc",
"accountPath": "m/84'/0'/0'",
"script": "p2wpkh",
"xpub": "xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V",
"derive": {
"receive": "0/*",
"change": "1/*"
}
},
{
"chain": "btc",
"accountPath": "m/86'/0'/0'",
"script": "p2tr",
"xpub": "xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ",
"derive": {
"receive": "0/*",
"change": "1/*"
}
},
{
"chain": "btc",
"accountPath": "m/49'/0'/0'",
"script": "p2sh-p2wpkh",
"xpub": "xpub6C6nQwHaWbSrzs5tZ1q7m5R9cPK9eYpNMFesiXsYrgc1P8bvLLAet9JfHjYXKjToD8cBRswJXXbbFpXgwsswVPAZzKMa1jUp2kVkGVUaJa7",
"derive": {
"receive": "0/*",
"change": "1/*"
}
},
{
"chain": "btc",
"accountPath": "m/44'/0'/0'",
"script": "p2pkh",
"xpub": "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
"derive": {
"receive": "0/*",
"change": "1/*"
}
},
{
"chain": "evm",
"accountPath": "m/44'/60'/0'",
"xpub": "xpub6DCoCpSuQZB2jawqnGMEPS63ePKWkwWPH4TU45Q7LPXWuNd8TMtVxRrgjtEshuqpK3mdhaWHPFsBngh5GFZaM6si3yZdUsT8ddYM3PwnATt",
"derive": "0/*"
},
{
"chain": "tron",
"accountPath": "m/44'/195'/0'",
"xpub": "xpub6D1AabNHCupeiLM65ZR9UStMhJ1vCpyV4XbZdyhMZBiJXALQtmn9p42VTQckoHVn8WNqS7dqnJokZHAHcHGoaQgmv8D45oNUKx6DZMNZBCd",
"derive": "0/*"
},
{
"chain": "sol",
"profile": "bip44-solana",
"accounts": [
{
"index": 0,
"profile": "standard",
"path": "m/44'/501'/0'/0'",
"address": "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk"
},
{
"index": 1,
"profile": "trust",
"path": "m/44'/501'/0'",
"address": "GjJyeC1r2RgkuoCWMyPYkCWSGSGLcz266EaAkLA27AhL"
},
{
"index": 2,
"profile": "legacy",
"path": "m/44'/501'",
"address": "D2PPQSYFe83nDzk96FqGumVU8JA7J8vj2Rhjc2oXzEi5"
}
]
}
]
}
QR 프레임 (ur:bytes, 4개 파트)
UR:BYTES/1-4/LPADAACFAMCECYJYHYKIWKHKADLTHKAMCFOLIEJNJLIEIHIEIYKPJZJZIOJTIHJYKTJLJPJEIOJNHSINJTJTIHJYIOKOIHJPJKINJLJTADISHSIAIAJLKPJTJYJKLTONIEKSJOKPIDKSJLKSJOKPIDENFXHSJYHGIEINHTINJLIEJNGOIHGHFYJOETGSGHECJLJPETJTJNIDGRGLIAKPKKKOKNEMHGKKJEJKHFFGJEGRFWEEGMFDKTFXFYEOHDKKKPKOGDFEIDKOJSFPGYHKEOJPFPGDJKISHGIAGTGSJLGDEYIYGTFGGTGRFDGDGEEEHTIHHTHDHKHFGOISGSKOEHHFGTJPIMGDFXEMGDHGENHFIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIYJOEYKTJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLETEEDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFWIOFWIOJKIHJKJOHGKOFEGMFGEOGSFDGYKPENFXJTJSIEKOIYFEKOJYGTIAGYIMHKJPIAGMKNKSECEOGYGEIMGUKSHSJPIMEYHSIYHKHGIAGSJYIHJLFLHFJEKKEMFYEOGOGRFYGDESGYKKJPGSJOJPGYEOHFFXFEFXJLHKEEESKKIYIEFYFEFDFLFXJYGTGTIMESEYJOGMIHGOJKGYIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHUYSWLYTA
UR:BYTES/2-4/LPAOAACFAMCECYJYHYKIWKHKADLTIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIEJOEYJYJPJEHSIAIAJLKPJTJYGDHSJYISJEJNDLETENDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFXENJTGYKTFDHSHGIDGUJPKNJKECJYHTEHJSEMJNECGMESIAGDGRESIHHKJOGLGTFGIHJKINHDJKHKJPIOIAEHGDETIDKOGSGSFPIHJYESGEIYFDIMHKHDGRIMGHJLFYETIAFWGMJKKTGEHDHDIDIDFGJOHDIOKTJKJKKTHFGDFPHTKNGRGTHSEHIMGOJOEYJEHFJEFLHFGOHSGEHSEMIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYJEJOEYJKISDPJOEYKTJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLEEESDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFWJLJKIYFXJTINIYKNKSIAFGKTJPGUKNGYINJSKPEYFYFWHFGHJKISJEFXHDHSIAKOGLJKHGFLHKGEHFHFISISHSKTFPEMIEEEGMECHGGUHGFLFGGLIDINETFPKTENHTGMIAEHIDJPKSGTKKHGGTKNFLEOFYGUGUGUGUJLIHJEJEKPIEISGOIEESKKGSIDENJSKSEOESGHESJTGTIEIMIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHBZSSDEKE
UR:BYTES/3-4/LPAXAACFAMCECYJYHYKIWKHKADLTOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIHJOEYJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLEEEEDIDLDYDIDLDYDIOXIEKSJOKPIDKSJLKSJOKPIDENFYFXJLFXJOGUKPGYHTFWEYIMHSKTJSJTFLGTFEGDGUENEOIHGDGRHGJEKTHGGDFDEEGHGOEEECGYEMGSGDHDHGKPGLIEETGHGTJYHFKSGMJPIOIMJYFEJKISKPJSJOGREOJNIEISHSHGFDGDFGJKFWJTIOISECFLFGHTHSGTENJKINEOKKHTIEGOJKGHETIEIEHKGTEOGDKTJTFPGHJYIHIAISHSINJTIAIHKOJNIYIEIHJPINKOIHIADYDLDRJEHSIAIAJLKPJTJYGDHSJYISJZJNDLEEEEDIDLENDYDIDLDYDIOXIEKSJOKPIDKSJLKSJOKPIDENFYEHFPHSIDGLFDFXKPJOIHINGSGTENECHTGMESGOGUJYGTISGEEHKOFXJOKKHFEEHDIDHTIEKKISGTHTFWINGEHDFPGSGYJYJNJTESJOEEEYHFGHGYIAJEJLFDHFJTETHGGLJSGUEMIEJSJTGEJLJEHTFDFPFDIAFDFLJLHSGYIOJNKOETFYEEECJLGLGOGRKSENFYHTGTGLHTFWFXIEIHIAISHSINJTIEJYJPJLJTIYIEIHJPINKOIHIADYDLDRJEHSIAIAJLKPJTJYGDHSJYISJNJNDLEEEEDIDLEHESECDIDLPRFLWYJT
UR:BYTES/4-4/LPAAAACFAMCECYJYHYKIWKHKADLTDYDIOTIHIAISHSINJTIAJKJLJZIOJOJPJLIYINJZIHJZIDINJOEEEEDPJKJLJZHSJTHSISHSIAIAJLKPJTJYJKLSOXIEJOHSJYISJOJNDLEEEEDIDLECDYEHDIDLDYDIDLDYDIIHINJTIEIHKSAEIOHSIEIEJPIHJKJKKSDWFDFPIOJEEHEEGEJOGTGYGSIOJYENJPHFIOKOEMIAFWGYFGGEHGFGJYJLECFYJSKSINEEEMEYKPGHEOFYGRJOJSJEIOJOJPJLIYINJZIHISJKJYHSJTIEHSJPIEOXIEJOHSJYISJNJNDLEEEEDIDLECDYEHDIDLDYDIIHINJTIEIHKSADIOHSIEIEJPIHJKJKKSDWFLIMGEKKIHFXEHJPEYGMIOJEKPJLFXHGGTKKGDHKJEFXHGGUFLGUFLGSIAKNEYENENFEHSFPJEGSFPEYEMFPISGSIOJOJPJLIYINJZIHIHJYJPKPJKJYOXIEJOHSJYISIMJNDLEEEEDIDLECDYEHDIIHINJTIEIHKSAOIOHSIEIEJPIHJKJKKSDWFYEYGDGDGYGUHKFGIHETEOJTFYKNJEESENFGJSFLKPJNHFGOETGEFPEMGEETKOIMEYGMISIMIAEYJLHDKNFEINECIOJOJPJLIYINJZIHIYJZIHIOHSIAKKISJOJPJLJYJLIAJLJZKPIDINJYJYJLJLJZDPJKINIOJTIHJPDPKTHSJZJZIHJYJSJNHSJKJYIHJPFGINJTIOIHJPJOJPINJTJYISEMEOIAECIEHSDYHSVOTIMSMD
내부 정규 CBOR(hex)과 SHA-256
a6646d6f64656466756c6c676e6574776f726b676d61696e6e65746776657273696f6e01686163636f756e747387a56478707562786f78707562364361745764695a696f646d5565544470384c54356f72386e6d624b4e637579767a3757796b7356466b4b423452487743443358797576504562767141515933724150736857634d4c6f5032664d464d4b48504a345a655a58595655684c7631564d726a5043375057365665636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a6673637269707466703277706b686b6163636f756e74506174686b6d2f3834272f30272f3027a56478707562786f787075623642674267736573705776455246334c48517536436e716476664576744d63516a597263527a783533514a6a537861726a3261665957634c74656f47566b79374433554b4450395179724c70725133564345436f5934397966644445484743744d4d6a393270526555735165636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a6673637269707464703274726b6163636f756e74506174686b6d2f3836272f30272f3027a56478707562786f787075623643366e51774861576253727a7335745a3171376d35523963504b396559704e4d4665736958735972676331503862764c4c416574394a66486a59584b6a546f443863425273774a5858626246705867777373775650415a7a4b4d61316a5570326b566b475655614a613765636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a667363726970746b703273682d703277706b686b6163636f756e74506174686b6d2f3439272f30272f3027a56478707562786f7870756236426f7366436e69667a7863467772537a51697175324442565473686b43586163764e735747594a56566868617741376434523557535747464e6269384177365a5263316272784d79574d7a473344535353536f656b6b756468556439794c62367178333954396e4d646a65636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a66736372697074657032706b686b6163636f756e74506174686b6d2f3434272f30272f3027a46478707562786f787075623644436f43705375515a42326a6177716e474d455053363365504b576b77575048345455343551374c505857754e6438544d7456785272676a744573687571704b336d6468615748504673426e67683547465a614d36736933795a64557354386464594d3350776e41547465636861696e6365766d6664657269766563302f2a6b6163636f756e74506174686c6d2f3434272f3630272f3027a46478707562786f787075623644314161624e4843757065694c4d36355a52395553744d684a3176437079563458625a6479684d5a42694a58414c51746d6e39703432565451636b6f48566e38574e71533764716e4a6f6b5a4841486348476f6151676d76384434356f4e554b7836445a4d4e5a42436465636861696e6474726f6e6664657269766563302f2a6b6163636f756e74506174686d6d2f3434272f313935272f3027a365636861696e63736f6c6770726f66696c656c62697034342d736f6c616e61686163636f756e747383a46470617468706d2f3434272f353031272f30272f302765696e646578006761646472657373782c4841676b31344a704d514c6774367256677637634251464a5746746f3544717869343732755433444b70716b6770726f66696c65687374616e64617264a464706174686d6d2f3434272f353031272f302765696e646578016761646472657373782c476a4a79654331723252676b756f43574d7950596b4357534753474c637a3236364561416b4c41323741684c6770726f66696c65657472757374a464706174686a6d2f3434272f3530312765696e646578026761646472657373782c44325050515359466538336e447a6b3936467147756d5655384a41374a38766a3252686a63326f587a4569356770726f66696c65666c65676163796870726f746f636f6c75626974746f6f6c2d7369676e65722d77616c6c6574716d617374657246696e6765727072696e74683733633564613061
c8db82cd92e0cf45cb6329b2429494b04a3b6bad7653c9bed1d2a5ef10f4a5fc
BitTool Signer가 QR 코드 아래에 표시하는 각 계정의 첫 번째 주소는 다음과 같습니다.
| 계정 | 경로 | 주소 |
|---|---|---|
| Bitcoin P2WPKH | m/84'/0'/0'/0/0 | bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu |
| Bitcoin P2TR | m/86'/0'/0'/0/0 | bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr |
| Bitcoin P2SH-P2WPKH | m/49'/0'/0'/0/0 | 37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf |
| Bitcoin P2PKH | m/44'/0'/0'/0/0 | 1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA |
| EVM | m/44'/60'/0'/0/0 | 0x9858EfFD232B4033E47d90003D41EC34EcaEda94 |
| TRON | m/44'/195'/0'/0/0 | TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH |
| Solana standard | m/44'/501'/0'/0' | HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk |
| Solana trust | m/44'/501'/0' | GjJyeC1r2RgkuoCWMyPYkCWSGSGLcz266EaAkLA27AhL |
| Solana legacy | m/44'/501' | D2PPQSYFe83nDzk96FqGumVU8JA7J8vj2Rhjc2oXzEi5 |
디코딩
이 디코더는 UR 계층에 @ngraveio/bc-ur를, CBOR에는 strict 모드의 cborg를 사용합니다.
import { URDecoder } from '@ngraveio/bc-ur';
import { decode } from 'cborg';
export function decodeWalletPairing(frames) {
const decoder = new URDecoder();
for (const frame of frames) decoder.receivePart(frame); // any order; repeats are ignored
if (!decoder.isComplete()) throw new Error(`Keep scanning: ${Math.round(decoder.estimatedPercentComplete() * 100)}%`);
if (!decoder.isSuccess()) throw new Error(decoder.resultError());
const ur = decoder.resultUR();
if (ur.type !== 'bytes') throw new Error('Not a BitTool Signer wallet QR');
const strict = { strict: true, rejectDuplicateMapKeys: true };
const inner = decode(ur.cbor, strict); // layer 1: a CBOR byte string
if (!(inner instanceof Uint8Array)) throw new Error('ur:bytes must hold a byte string');
const pairing = decode(inner, strict); // layer 2: the text-keyed map
if (pairing.protocol !== 'bittool-signer-wallet') throw new Error('Unknown protocol');
if (pairing.version !== 1) throw new Error('Unsupported version');
if (pairing.mode !== 'full' && pairing.mode !== 'simple') throw new Error('Unsupported mode');
if (pairing.network !== 'mainnet') throw new Error('Unsupported network');
if (!/^[0-9a-f]{8}$/.test(pairing.masterFingerprint)) throw new Error('Bad fingerprint');
return pairing;
}
그런 다음 주소를 파생합니다. Bitcoin은 네 가지 스크립트 유형을 사용합니다. EVM과 TRON은 압축되지 않은 공개 키를 Keccak-256으로 해시해 마지막 20바이트를 사용합니다(TRON은 0x41 접두사를 붙이고 Base58Check로 인코딩). Solana 주소는 이미 페이로드에 들어 있습니다.
import { HDKey } from '@scure/bip32';
import { keccak_256 } from '@noble/hashes/sha3.js';
import { sha256 } from '@noble/hashes/sha2.js';
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { base58check } from '@scure/base';
import { p2wpkh, p2tr, p2sh, p2pkh } from '@scure/btc-signer';
import { getAddress } from 'ethers';
const child = (xpub, branch, index) => HDKey.fromExtendedKey(xpub).deriveChild(branch).deriveChild(index);
export function firstAddresses(pairing) {
const out = {};
for (const account of pairing.accounts) {
if (account.chain === 'btc') {
const pub = child(account.xpub, 0, 0).publicKey; // receive branch 0, index 0
out[`btc:${account.script}`] = {
p2wpkh: () => p2wpkh(pub).address,
p2tr: () => p2tr(pub.subarray(1)).address, // BIP86: untweaked x-only key
'p2sh-p2wpkh': () => p2sh(p2wpkh(pub)).address,
p2pkh: () => p2pkh(pub).address,
}[account.script]();
} else if (account.chain === 'evm' || account.chain === 'tron') {
const point = secp256k1.Point.fromBytes(child(account.xpub, 0, 0).publicKey);
const hash = keccak_256(point.toBytes(false).subarray(1)).subarray(-20);
out[account.chain] = account.chain === 'evm'
? getAddress('0x' + Buffer.from(hash).toString('hex'))
: base58check(sha256).encode(Uint8Array.from([0x41, ...hash]));
} else if (account.chain === 'sol') {
for (const sol of account.accounts) out[`sol:${sol.profile}`] = sol.address;
}
}
return out;
}
두 함수 모두 문서 저장소의 examples/test.mjs가 위 테스트 벡터로 실행해 검증합니다.
수신 측 규칙
견고한 수신 측 구현은 다음을 지켜야 합니다.
protocol == "bittool-signer-wallet", 정수version == 1,mode == "full",network == "mainnet", 유효한 16진수 8자리masterFingerprint를 필수로 확인하세요. 어떤 검사가 실패했는지 명확히 알려 주세요.- 계정은 위치가 아니라 키로 찾고, 알려진 계정이 빠졌거나 중복된 페이로드는 거부하세요.
- 알 수 없는 필드는 무시하세요(이후의 v1 페이로드에 추가될 수 있습니다). 받은 바이트 그대로 검사하세요. 알 수 없는 필드를 제거하고 다시 인코딩한 뒤 정규 인코딩 검사를 하면 유효한 새 버전 페이로드까지 거부하게 됩니다.
- 모든 xpub을 검증하세요: Base58Check, 버전
0x0488B21E, 깊이 3, 강화(hardened) 자식 인덱스, 유효한 압축 secp256k1 키.xprv,tpub,ypub/zpub은 거부하세요. - 알 수 없는 경로를 기본값으로 조용히 바꾸지 말고, 위 표를 기준으로 경로를 확인하세요.
- Solana 키를 검증하세요: Base58 디코딩 결과가 정확히 32바이트여야 하고, 유효한 Ed25519 점이어야 하며, 중복이 없어야 합니다.
- 신뢰할 수 없는 입력으로 취급하세요: 프레임 수와 크기에 상한을 두고, 중복된 CBOR 키와 뒤따르는 잉여 바이트를 거부하세요. BitTool Signer의 자체 참조 디코더는 정규 형식이 아닌 CBOR도 거부합니다.
xpub의 구조는 확인할 수 있지만 출처는 확인할 수 없습니다. 페이로드의 어떤 내용도 키가 명시된 지문이나 경로에서 나왔음을 증명하지 못하며, 그래서 사용자가 주소를 비교해야 합니다.
보안 참고 사항
- 페이로드에는 확장 공개 키와 주소만 들어 있습니다. 서명 기기의 인증된 공개 인덱스로 만들며, 볼트(vault)는 열지 않습니다.
- 이 코드를 스캔한 사람은 누구나 이 계정들의 모든 주소와 전체 내역을 볼 수 있습니다. 다른 xpub과 마찬가지로 다루세요. 로그에 남기거나, 분석 서비스에 업로드하거나, 크래시 보고서에 포함하지 마세요.
- QR 코드에는 서명이 없으며, 수신하는 지갑을 인증하지도 않습니다. 사용자가 주소를 비교하는 것이 검증 수단입니다.