MultiSig 다중서명

MultiSig 다중서명

MultiSig는 multiple signature를 의미하는데, 복수 개의 키를 사용해야 Bitcoin을 사용할 수 있는(소비할 수 있는) 기능이다.

비트코인의 MultiSig는 m-of-n multiple signature를 사용하는데, 총 n개의 키 중에 m개만 있으면 코인을 소비할 수 있다. 이때, m은 항상 n보다 작거나 같게 된다. 예를 들어, 2-of-5 MultiSig의 경우 5개의 키중 2개만 있으면 코인을 사용할 수 있다. 이는 다섯명이 각각의 키를 가지고 있을 때, 다섯명 중 두명만 동의하면 코인을 사용할 수 있는 것으로 생각할 수 있다.

createmultisig 명령을 사용한 MultiSig 주소 생성

MultiSig를 사용하기 위해서는 먼저 MultiSig 주소를 생성하여야 한다. MultiSig는 기본적으로 P2SH 주소를 사용하는데, 복수 소유자의 Public Key들을 사용하여 Script Hash를 만들고, 이 Script Hash의 주소(P2SH 주소)에 코인을 보내도록 한다.

MultiSig는 여러 명이 다른 컴퓨터에서 키를 가지고 있는 것이 일반적이겠지만, 여기서는 간단히 로컬에서 2개의 키를 생성하여 이들로부터 MultiSig 주소를 만들어 본다. 아래는 로컬 월렛에서 $ADDR1, $ADDR2등 2개의 주소를 얻어내고, 이들의 public key를 getaddressinfo 명령에서 찾아낸 후, createmultisig 명령을 사용하여 P2SH 주소를 생성하는 예이다.

$ ADDR1=$(bitcoin-cli getnewaddress)
$ ADDR2=$(bitcoin-cli getnewaddress)
$ echo $ADDR1, $ADDR2
tb1q4upnwclcenqecva6mhh9uaerdrg8gx7mxj4fsq, tb1qvgqcmkn0eyrr2mzlzql5vl3tnukjk7ykrtjaem
$ PUBKEY1=$(bitcoin-cli getaddressinfo $ADDR1 | jq -r '.pubkey')
$ PUBKEY2=$(bitcoin-cli getaddressinfo $ADDR2 | jq -r '.pubkey')
$ bitcoin-cli createmultisig 2 '''["'$PUBKEY1'","'$PUBKEY2'"]'''
{
  "address": "2N1cx6x93ZK1f3djnJEneY5ZF8HeTavC3qG",
  "redeemScript": "522102814e05e374bba080ae63bd0570e4deb448fc8bfd61deadb73fdf908e8bafcaa12102738ae71a7b336061dcc1edfef97768bb9d6c170bfbd4b3b01a1bca62392cca8552ae",
  "descriptor": "sh(multi(2,02814e05e374bba080ae63bd0570e4deb448fc8bfd61deadb73fdf908e8bafcaa1,02738ae71a7b336061dcc1edfef97768bb9d6c170bfbd4b3b01a1bca62392cca85))#uyxy5lht"
}

createmultisig 명령은 public key를 받아들이므로, 비트코인 주소가 아닌 그 주소의 public key를 알아야 한다. 비트코인 주소는 public key를 해싱해서 만들기 때문에, 월렛을 가지고 있지 않으면 주소로부터 public key를 만들어 낼 수 없게 된다.

createmultisig의 첫번째 파라미터는 nrequired 인데, 이는 m-of-n 에서의 m 을 의미한다. 즉, 코인을 사용하기 위해 전체 n 중에 m개의 키가 필요하다는 것을 의미한다. 여기서는 2-of-2 multisig 로서 m=2 가 된다.

createmultisig 출력의 address 필드는 P2SH 주소로서 여기에 코인을 보내게 된다. 그리고 redeemScript 는 나중에 코인을 소비하기 위한 스크립트이므로 이를 보관해야 한다. createmultisig 명령에서 중요한 점은 public key 배열 아규먼트의 순서가 중요하다는 점이다. 이 순서가 바뀌면 redeemScript가 바뀌고, 해시값인 P2SH 주소도 바뀐다. BIP-67 에서 pubkey의 순서를 순차적으로 맞췌 스크립트를 생성할 것을 제안하였는데, 모든 월렛이 지원하는 것은 아니므로 체크할 필요가 있다. (참고: BIP-67 Deterministic Pay-to-script-hash multi-signature addresses through public key sorting)

일단 P2SH 주소가 생성되면, Sender는 P2PKH 혹은 Segwit 주소와 동일한 방식으로 코인을 보내면 된다.

MultiSig 주소에 보내진 코인을 사용하기 위해서는, MultiSig 주소(P2SH 주소)와 redeemScript 정보가 필요하니 이를 저장해 두어야 한다.

MultiSig 코인 사용하기

MultiSig 주소로 보내진 코인을 사용하기 위해서는 m-of-n multisignature 에서 m 만큼의 Key를 사용하여 Sign 해야 한다. 예를 들어, 위에서 사용한 2-of-2 multisig 인 경우, 2개의 Key를 사용하여 2번 서명하여야 한다.

먼저 createmultisig로 생성된 MultiSig 주소는 월렛에 자동으로 넣어지지 않기 때문에(주: addmultisigaddress 명령은 월렛에 넣음) 아래와 같이 importaddress 명령을 사용하여 MultiSig 주소를 월렛에 추가한다. 이렇게 하면 listunspent에서 Balance와 UTXO 정보를 볼 수 있다.

    $ bitcoin-cli importaddress 2N1cx6x93ZK1f3djnJEneY5ZF8HeTavC3qG

MultiSig 주소에 있는 UTXO를 소비하기 위해 2개의 Key로 2번의 서명을 해야하는데, 아래와 같이 각 Signer는 차례로 Raw Transaction을 Sign하면 된다.

// UTXO
$ txid="91ec7a7ada095fe2f32767252c0c8d86191a7f687b2a12d7c92f55d4d29f300d"
$ vout=1

// scriptPubKey (gettransaction 혹은 listunspent에 있음)
$ spk="a9145bdce5b3151e5501a0ee0039fc63c5535c7b868387"

// redeemScript (앞에서 저장해 둔 redeemScript)
$ redeem="522102814e05e374bba080ae63bd0570e4deb448fc8bfd61deadb73fdf908e8bafcaa12102738ae71a7b336061dcc1edfef97768bb9d6c170bfbd4b3b01a1bca62392cca8552ae"

// 새 수신자
$ recipient=$(bitcoin-cli getrawchangeaddress)

// createrawtransaction (UTXO를 사용하는 TX 만듦)
$ rawtx=$(bitcoin-cli -named createrawtransaction inputs='''[ { "txid": "'$txid'", "vout": '$vout' } ]''' outputs='''{ "'$recipient'": 0.00099}''')

// 첫번째 private key 
$ priv1=$(bitcoin-cli dumpprivkey $ADDR1)

// signrawtransactionwithkey (첫번째 서명)
// createrawtransaction에서 생성된 raw transaction을 첫번째 키 priv1을 사용하여 서명함.
// 이전 트랜잭션(prevtxs)에 scriptPubKey, redeemScript를 함께 지정함.
$ rawsign1=$(bitcoin-cli -named signrawtransactionwithkey hexstring=$rawtx privkeys='''["'$priv1'"]''' prevtxs='''[{ "txid": "'$txid'", "vout": '$vout', "scriptPubKey": "'$spk'", "redeemScript": "'$redeem'" }]''' | jq -r '.hex')

// 위의 signrawtransactionwithkey 명령은 다음과 같은 출력을 내는데,
// complete=false, 즉 더 서명해야 함을 알 수 있다.  
{
    "hex": "02000000010d309fd2d4552fc9d7122a7b687f1a19868d0c2c256727f3e25f09da7a7aec9101000000920047304402207a98869095142aef118f8b00c2805b95e73b5adf97f2c8da2ba0bef4af00792f02201ea2ee4e2eb5c26384458e47c7f391ef27e6ed3686deffc69f7f3eb4ef72a374010047522102814e05e374bba080ae63bd0570e4deb448fc8bfd61deadb73fdf908e8bafcaa12102738ae71a7b336061dcc1edfef97768bb9d6c170bfbd4b3b01a1bca62392cca8552aeffffffff01b8820100000000001600144d0f8a20c0ea265c73e56f30351de0dea55aae8800000000",
    "complete": false,
    "errors": [
    {
        "txid": "91ec7a7ada095fe2f32767252c0c8d86191a7f687b2a12d7c92f55d4d29f300d",
        "vout": 1,
        "witness": [
        ],
        "scriptSig": "0047304402207a98869095142aef118f8b00c2805b95e73b5adf97f2c8da2ba0bef4af00792f02201ea2ee4e2eb5c26384458e47c7f391ef27e6ed3686deffc69f7f3eb4ef72a374010047522102814e05e374bba080ae63bd0570e4deb448fc8bfd61deadb73fdf908e8bafcaa12102738ae71a7b336061dcc1edfef97768bb9d6c170bfbd4b3b01a1bca62392cca8552ae",
        "sequence": 4294967295,
        "error": "CHECK(MULTI)SIG failing with non-zero signature (possibly need more signatures)"
    }
    ]
}

// 두번째 private key 얻기
$ priv2=$(bitcoin-cli dumpprivkey $ADDR2)
// 두번째 서명으로 여기서는 hexstring 에 첫번째 서명의 hex 출력값을 사용하고,
// priv2 키를 사용하여 서명한다. 
$ rawsign2=$(bitcoin-cli -named signrawtransactionwithkey hexstring=$rawsign1 privkeys='''["'$priv2'"]''' prevtxs='''[{ "txid": "'$txid'", "vout": '$vout', "scriptPubKey": "'$spk'", "redeemScript": "'$redeem'" }]''' | jq -r '.hex')
// 두번째 서명으로 아래와 같이 출력이 만들어 지는데
// complete=true 즉 서명이 완료된 것이다. 
{
    "hex": "02000000010d309fd2d4552fc9d7122a7b687f1a19868d0c2c256727f3e25f09da7a7aec9101000000d90047304402207a98869095142aef118f8b00c2805b95e73b5adf97f2c8da2ba0bef4af00792f02201ea2ee4e2eb5c26384458e47c7f391ef27e6ed3686deffc69f7f3eb4ef72a3740147304402203ec883c49fdb68c030bfb5a2c8bb0103e6fe5895f81d927e74f1dabb2cba00e002201a2f0dd18abc39e1d07a65c798183719921415dd9543230145f262c139295ef80147522102814e05e374bba080ae63bd0570e4deb448fc8bfd61deadb73fdf908e8bafcaa12102738ae71a7b336061dcc1edfef97768bb9d6c170bfbd4b3b01a1bca62392cca8552aeffffffff01b8820100000000001600144d0f8a20c0ea265c73e56f30351de0dea55aae8800000000",
    "complete": true
}

// 서명이 완료된 트랜잭션을 sendrawtransaction 명령으로 보낸다. 
$ bitcoin-cli sendrawtransaction $rawsign2

addmultisigaddress 명령을 사용한 MultiSig 주소 생성 (1)

addmultisigaddress 명령은 v0.10.0 이상에서 제공되는 명령으로, createmultisig 명령은 다중서명을 위한 P2SH 주소와 redeemScript를 출력해 주지만, addmultisigaddress 명령은 P2SH 주소와 redeemScript 생성에 덧붙여 몇가지 정보를 월렛에 넣어 주는 기능을 갖는다.

createmultisig 명령은 월렛에 어떤 정보도 넣지 않기 때문에, 일반적으로 수작업으로 P2SH 주소를 월렛에 import 해 주고, 또한 redeemScript를 별도로 저장해야 한다. 하지만, 새로 추가된 addmultisigaddress 명령은 이를 좀 더 간편하게 한 명령으로, redeemScript, P2SH 주소 등을 월렛에 자동으로 저장해 준다.

createmultisig 명령은 항상 public key를 사용해야 하지만, addmultisigaddress 명령은 public key 혹은 Bitcoin 주소를 사용할 수 있다. 만약 해당 Bitcoin 주소를 자신의 월렛에서 생성했다면, 월렛은 public key를 가지고 있을 것이므로, addmultisigaddress 명령에서 public key 대신 Bitcoin 주소를 사용할 수 있다. 물론 주소가 다른 머신에서 생성되었다면, 자신의 월렛에 이 정보가 없을 것이므로 public key를 사용해야 한다.

아래는 addmultisigaddress를 사용하여, 하나의 월렛에 있는 로컬 주소에 대해 2-of-2 다중서명을 위한 P2SH를 생성하고, 이를 소비하는 것을 예시한 것이다.

// 새로운 2개의 주소 얻기
$ ADDR1=$(bitcoin-cli getnewaddress)
$ ADDR2=$(bitcoin-cli getnewaddress)
$ echo $ADDR1, $ADDR2
tb1qjwvwn3cpcw7lc3xuhq043glejlhqm6kevw8cuv, tb1qgnq62z5k4g4fa9lvu3jx528sk2ummut6h5ujns

// addmultisigaddress로 P2SH 주소 생성 (2-of-2)
$ bitcoin-cli addmultisigaddress 2 '''["'$ADDR1'","'$ADDR2'"]''' "" "legacy"
{
    "address": "2N2yscVuoPWcLSwmDRPTchjJ2PfksSwySjW",
    "redeemScript": "5221024a7a35b9a40791a438c0e247c5b0bd5b0e08c3195dfa6c081b7ca86ad299d9fc2103f41a22c65a5b03d342dbda302c71f8acc2cef6c89434956f78680fdece224ebb52ae",
    "descriptor": "sh(multi(2,[9d1456ef/0'/0'/27']024a7a35b9a40791a438c0e247c5b0bd5b0e08c3195dfa6c081b7ca86ad299d9fc,[9d1456ef/0'/0'/28']03f41a22c65a5b03d342dbda302c71f8acc2cef6c89434956f78680fdece224ebb))#pk6ae9qq"
}

$ bitcoin-cli getaddressinfo 2N2yscVuoPWcLSwmDRPTchjJ2PfksSwySjW
{
  "address": "2N2yscVuoPWcLSwmDRPTchjJ2PfksSwySjW",
  "scriptPubKey": "a9146aca027ce64403ee6be4b898ea81ef9321c4dd1687",
  "ismine": true,  ====> 월렛에 있음 표시
  "solvable": true,
   ... 생략 ...
}

// P2SH 주소에 코인 전달
$ bitcoin-cli sendtoaddress 2N2yscVuoPWcLSwmDRPTchjJ2PfksSwySjW 0.002

// P2SH 주소 코인 사용(Spend) 
$ utxo="755949358820ddd3adf598b152344b82d2b1aa85da1b75e7fa8a025b6b84094f"
$ vout=1
$ receiver=$(bitcoin-cli getrawchangeaddress)
tb1qp9awv3jqtulaxmpwcv7599zfufxj2a687ah0qr

$ rawtx=$(bitcoin-cli -named createrawtransaction inputs='''[ { "txid": "'$txid'", "vout": '$vout' } ]''' outputs='''{ "'$receiver'": 0.00199}''')

// ADDR1, ADDR2에 대한 private key가 모두 하나의 로컬 월렛에 있으므로
// 아래와 같이 하나의 signrawtransactionwithwallet 을 사용해서 동시 서명이 가능하다. 
$ signedtx=$(bitcoin-cli signrawtransactionwithwallet $rawtx | jq -r '.hex')

$ bitcoin-cli sendrawtransaction $signedtx    

위의 예에서 signrawtransactionwithwallet 을 수행할 때, 2개의 ADDR1, ADDR2가 모두 로컬의 월렛으로부터 생성되었으므로 월렛이 2개의 private key를 가지고 있어 한번의 Sign 만으로 서명이 완료된다. 만약 여러 머신에서 서명을 수행해야 한다면, 각 머신에서 돌아가면서 서명을 수행하게 되고, 최종적으로 서명 완료된 Hex String을 sendrawtransaction 에서 사용할 것이다.

addmultisigaddress 명령을 사용한 복수 월렛의 MultiSig (2)

위와 같이 하나의 월렛이 아닌, 복수 머신 혹은 복수 월렛으로부터 서명하는 MultiSig 절차는 다음과 같다.

// 2개의 다른 월렛에서 수신 주소 생성 
$ ADDR1=$(bitcoin-cli -rpcwallet="" getnewaddress)
$ ADDR2=$(bitcoin-cli -rpcwallet=test2 getnewaddress)
$ echo $ADDR1, $ADDR2
tb1qtzxr7k0mjdxfsxtfmfjd9awe8fa7f7k3l2pe9f, tb1qwqgggvku4c20jrxn7ykm9xaln2vzryjtpj6n4e

// addmultisigaddress 명령은 자신의 월렛주소인 경우 주소를 사용할 수 있지만,
// 다른 월렛의 주소에 대해서는 public key를 사용한다. 
$ PUBKEY1=$(bitcoin-cli -rpcwallet="" getaddressinfo $ADDR1 | jq -r '.pubkey')
$ PUBKEY2=$(bitcoin-cli -rpcwallet="test2" getaddressinfo $ADDR2 | jq -r '.pubkey')
$ echo $PUBKEY1, $PUBKEY2
02aa9ee46823e39072dfb5396e5cdcd4f1fea55b4238a73686545c2b7db80b04f2, 
02bfd945cf58e8ebbbf9beee909f23f6394367ec31298ae3dbea27c5dcab28f2b5

// [월렛1]을 활용하여 MultiSig 주소 생성. 이때 P2SH 주소를 월렛에 들어가지 않음.
$ bitcoin-cli -rpcwallet="" addmultisigaddress 2 '''["'$ADDR1'","'$PUBKEY2'"]''' "" "legacy"
{
    "address": "2N3KUjJk7sNMJoR3BEppzGnJUyULANJRsQP",
    "redeemScript": "522102aa9ee46823e39072dfb5396e5cdcd4f1fea55b4238a73686545c2b7db80b04f22102bfd945cf58e8ebbbf9beee909f23f6394367ec31298ae3dbea27c5dcab28f2b552ae",
    "descriptor": "sh(multi(2,[9d1456ef/0'/0'/41']02aa9ee46823e39072dfb5396e5cdcd4f1fea55b4238a73686545c2b7db80b04f2,[70108432]02bfd945cf58e8ebbbf9beee909f23f6394367ec31298ae3dbea27c5dcab28f2b5))#cew2xvpe"
}
// [월렛2]를 활용하여 MultiSig 주소 생성 
$ bitcoin-cli -rpcwallet="test2" addmultisigaddress 2 '''["'$PUBKEY1'","'$ADDR2'"]''' "" "legacy"
{
    "address": "2N3KUjJk7sNMJoR3BEppzGnJUyULANJRsQP",
    "redeemScript": "522102aa9ee46823e39072dfb5396e5cdcd4f1fea55b4238a73686545c2b7db80b04f22102bfd945cf58e8ebbbf9beee909f23f6394367ec31298ae3dbea27c5dcab28f2b552ae",
    "descriptor": "sh(multi(2,[588c3f59]02aa9ee46823e39072dfb5396e5cdcd4f1fea55b4238a73686545c2b7db80b04f2,[de093b2d/0'/0'/4']02bfd945cf58e8ebbbf9beee909f23f6394367ec31298ae3dbea27c5dcab28f2b5))#k4rkrs5n"
}

// 각 월렛에 P2SH 주소 import
$ bitcoin-cli -named -rpcwallet="" importaddress address="2N3KUjJk7sNMJoR3BEppzGnJUyULANJRsQP" rescan=false  
$ bitcoin-cli -named -rpcwallet="test2" importaddress address="2N3KUjJk7sNMJoR3BEppzGnJUyULANJRsQP" rescan=false  

// MultiSig 주소에 4 mBTC 보냄
$ bitcoin-cli -rpcwallet="" walletpassphrase "mypw" 300
$ bitcoin-cli -rpcwallet="" sendtoaddress 2N3KUjJk7sNMJoR3BEppzGnJUyULANJRsQP 0.004
e22fb83df61fcac8e9394ca006bc7e71f956d715026dfd66b81b7c6dfce6a9a9

// UTXO 확인
$ bitcoin-cli -rpcwallet="" listunspent 
[
    {
    "txid": "e22fb83df61fcac8e9394ca006bc7e71f956d715026dfd66b81b7c6dfce6a9a9",
    "vout": 0,
    "address": "2N3KUjJk7sNMJoR3BEppzGnJUyULANJRsQP",
    "label": "",
    "redeemScript": "522102aa9ee46823e39072dfb5396e5cdcd4f1fea55b4238a73686545c2b7db80b04f22102bfd945cf58e8ebbbf9beee909f23f6394367ec31298ae3dbea27c5dcab28f2b552ae",
    "scriptPubKey": "a9146e7f3a3d37e6344e40dbe81e3efc1d85308bab6a87",
    "amount": 0.00400000,
    "confirmations": 8,
    "spendable": false,
    "solvable": true,
    "desc": "sh(multi(2,[9d1456ef/0'/0'/41']02aa9ee46823e39072dfb5396e5cdcd4f1fea55b4238a73686545c2b7db80b04f2,[70108432]02bfd945cf58e8ebbbf9beee909f23f6394367ec31298ae3dbea27c5dcab28f2b5))#cew2xvpe",
    "safe": true
    },
    ... 생략 ...
]

// MultiSig 사용하기 (spend)
$ recipient=$(bitcoin-cli -rpcwallet="" getnewaddress)
$ txid="e22fb83df61fcac8e9394ca006bc7e71f956d715026dfd66b81b7c6dfce6a9a9"
$ vout=0
$ rawtx=$(bitcoin-cli -named createrawtransaction inputs='''[ { "txid": "'$txid'", "vout": '$vout' } ]''' outputs='''{ "'$recipient'": 0.0039}''')

// [월렛1]에서 서명
$ bitcoin-cli -rpcwallet="" walletpassphrase "mypw" 300
$ signtx1=$(bitcoin-cli -rpcwallet="" signrawtransactionwithwallet $rawtx | jq -r '.hex')

// 이어 순차적으로 [월렛2]에서 서명
$ bitcoin-cli loadwallet test2
$ bitcoin-cli -rpcwallet=test2 walletpassphrase "mypw" 300
$ signtx2=$(bitcoin-cli -rpcwallet=test2 signrawtransactionwithwallet $signtx1 | jq -r '.hex')

// 최종 서명 완성된 TX 보냄 
$ bitcoin-cli sendrawtransaction $signtx2    
본 웹사이트는 광고를 포함하고 있습니다. 광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.