본문으로 건너뛰기

지갑 레벨 QR (bittool-signer-wallet v1)

ur:bytescanonical CBORanimatedversion 1

지갑 레벨 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

중요한 세부 사항은 네 가지입니다.

  1. CBOR 계층이 두 개입니다. UR 본문은 CBOR 바이트 문자열이고, 페어링 맵은 그 안에 들어 있습니다. 바깥 계층을 디코딩해 바이트를 얻은 다음, 그 바이트를 다시 디코딩하세요. JSON도, tag-24 항목도, ur:bittool-… 같은 사용자 정의 타입도 아닙니다.
  2. 텍스트 키를 사용합니다. 맵 키는 JSON 필드 이름을 CBOR 텍스트 문자열로 쓴 것이므로, CBOR과 아래 JSON 보기는 같은 데이터입니다.
  3. 정규(canonical) 인코딩입니다. 확정 길이, 가장 짧은 정수 형식을 사용하며, 맵 키는 인코딩된 바이트 기준으로 정렬합니다(텍스트 키라면 짧은 키가 먼저, 그다음 바이트 순). 텍스트, 부호 없는 정수, 배열, 맵만 나타나며, 값이 없는 필드는 생략합니다.
  4. 항상 애니메이션입니다. 프래그먼트 크기는 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

페어링 객체​

엔벨로프​

필드타입값
protocoltext"bittool-signer-wallet"
versionuint1. 다른 버전은 모두 거부하고, 더 높은 버전을 v1로 읽어서는 안 됩니다.
modetext"full". BitTool Signer는 full만 생성합니다.
networktext"mainnet"
masterFingerprinttextBIP32 마스터 키 지문, 소문자 16진수 8자리
accountsarray항목 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')
evmaccountPath m/44'/60'/0', xpub, derive: "0/*"모든 EVM 네트워크에 계정 하나
tronaccountPath m/44'/195'/0', xpub, derive: "0/*"
solprofile: "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에서 생성했습니다. 스캐너로 애니메이션을 비추거나 아래 프레임을 사용하세요.

지갑 레벨 페어링 QR (테스트 지갑)프레임 4개 · 1,561바이트 페이로드 · 앱처럼 반복 재생
UR:BYTES/1-4/LPADAACFAMCECYJYHYKIWKHKADLTHKAMCFOLIEJNJLIEIHIEIYKPJZJZIOJTIHJYKTJLJPJEIOJNHSINJTJTIHJYIOKOIHJPJKINJLJTADISHSIAIAJLKPJTJYJKLTONIEKSJOKPIDKSJLKSJOKPIDENFXHSJYHGIEINHTINJLIEJNGOIHGHFYJOETGSGHECJLJPETJTJNIDGRGLIAKPKKKOKNEMHGKKJEJKHFFGJEGRFWEEGMFDKTFXFYEOHDKKKPKOGDFEIDKOJSFPGYHKEOJPFPGDJKISHGIAGTGSJLGDEYIYGTFGGTGRFDGDGEEEHTIHHTHDHKHFGOISGSKOEHHFGTJPIMGDFXEMGDHGENHFIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIYJOEYKTJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLETEEDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFWIOFWIOJKIHJKJOHGKOFEGMFGEOGSFDGYKPENFXJTJSIEKOIYFEKOJYGTIAGYIMHKJPIAGMKNKSECEOGYGEIMGUKSHSJPIMEYHSIYHKHGIAGSJYIHJLFLHFJEKKEMFYEOGOGRFYGDESGYKKJPGSJOJPGYEOHFFXFEFXJLHKEEESKKIYIEFYFEFDFLFXJYGTGTIMESEYJOGMIHGOJKGYIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHUYSWLYTA
1 / 4
페어링 객체 (내부 CBOR의 JSON 보기)
{
"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
내부 CBOR, 1561바이트
a6646d6f64656466756c6c676e6574776f726b676d61696e6e65746776657273696f6e01686163636f756e747387a56478707562786f78707562364361745764695a696f646d5565544470384c54356f72386e6d624b4e637579767a3757796b7356466b4b423452487743443358797576504562767141515933724150736857634d4c6f5032664d464d4b48504a345a655a58595655684c7631564d726a5043375057365665636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a6673637269707466703277706b686b6163636f756e74506174686b6d2f3834272f30272f3027a56478707562786f787075623642674267736573705776455246334c48517536436e716476664576744d63516a597263527a783533514a6a537861726a3261665957634c74656f47566b79374433554b4450395179724c70725133564345436f5934397966644445484743744d4d6a393270526555735165636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a6673637269707464703274726b6163636f756e74506174686b6d2f3836272f30272f3027a56478707562786f787075623643366e51774861576253727a7335745a3171376d35523963504b396559704e4d4665736958735972676331503862764c4c416574394a66486a59584b6a546f443863425273774a5858626246705867777373775650415a7a4b4d61316a5570326b566b475655614a613765636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a667363726970746b703273682d703277706b686b6163636f756e74506174686b6d2f3439272f30272f3027a56478707562786f7870756236426f7366436e69667a7863467772537a51697175324442565473686b43586163764e735747594a56566868617741376434523557535747464e6269384177365a5263316272784d79574d7a473344535353536f656b6b756468556439794c62367178333954396e4d646a65636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a66736372697074657032706b686b6163636f756e74506174686b6d2f3434272f30272f3027a46478707562786f787075623644436f43705375515a42326a6177716e474d455053363365504b576b77575048345455343551374c505857754e6438544d7456785272676a744573687571704b336d6468615748504673426e67683547465a614d36736933795a64557354386464594d3350776e41547465636861696e6365766d6664657269766563302f2a6b6163636f756e74506174686c6d2f3434272f3630272f3027a46478707562786f787075623644314161624e4843757065694c4d36355a52395553744d684a3176437079563458625a6479684d5a42694a58414c51746d6e39703432565451636b6f48566e38574e71533764716e4a6f6b5a4841486348476f6151676d76384434356f4e554b7836445a4d4e5a42436465636861696e6474726f6e6664657269766563302f2a6b6163636f756e74506174686d6d2f3434272f313935272f3027a365636861696e63736f6c6770726f66696c656c62697034342d736f6c616e61686163636f756e747383a46470617468706d2f3434272f353031272f30272f302765696e646578006761646472657373782c4841676b31344a704d514c6774367256677637634251464a5746746f3544717869343732755433444b70716b6770726f66696c65687374616e64617264a464706174686d6d2f3434272f353031272f302765696e646578016761646472657373782c476a4a79654331723252676b756f43574d7950596b4357534753474c637a3236364561416b4c41323741684c6770726f66696c65657472757374a464706174686a6d2f3434272f3530312765696e646578026761646472657373782c44325050515359466538336e447a6b3936467147756d5655384a41374a38766a3252686a63326f587a4569356770726f66696c65666c65676163796870726f746f636f6c75626974746f6f6c2d7369676e65722d77616c6c6574716d617374657246696e6765727072696e74683733633564613061
SHA-256(inner)
c8db82cd92e0cf45cb6329b2429494b04a3b6bad7653c9bed1d2a5ef10f4a5fc

BitTool Signer가 QR 코드 아래에 표시하는 각 계정의 첫 번째 주소는 다음과 같습니다.

계정경로주소
Bitcoin P2WPKHm/84'/0'/0'/0/0bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu
Bitcoin P2TRm/86'/0'/0'/0/0bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr
Bitcoin P2SH-P2WPKHm/49'/0'/0'/0/037VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf
Bitcoin P2PKHm/44'/0'/0'/0/01LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA
EVMm/44'/60'/0'/0/00x9858EfFD232B4033E47d90003D41EC34EcaEda94
TRONm/44'/195'/0'/0/0TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH
Solana standardm/44'/501'/0'/0'HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk
Solana trustm/44'/501'/0'GjJyeC1r2RgkuoCWMyPYkCWSGSGLcz266EaAkLA27AhL
Solana legacym/44'/501'D2PPQSYFe83nDzk96FqGumVU8JA7J8vj2Rhjc2oXzEi5

디코딩​

이 디코더는 UR 계층에 @ngraveio/bc-ur를, CBOR에는 strict 모드의 cborg를 사용합니다.

examples/wallet-pairing.mjs
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 주소는 이미 페이로드에 들어 있습니다.

examples/wallet-pairing.mjs
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 코드에는 서명이 없으며, 수신하는 지갑을 인증하지도 않습니다. 사용자가 주소를 비교하는 것이 검증 수단입니다.