Let's start from multisig 2-of-3 redeem_script
template:
OP_2 OP_DATA_33 key1.public_key OP_DATA_33 key2.public_key OP_DATA_33 key3.public_key OP_3 OP_CHECKMULTISIG
which ties the keys together.
All you need to do here is generate some 3 keys (33 bytes each) from those xpubs (BIP-0032) and concatenate the keys with opcode bytes.
Raw bytes of this script would be (note: the + operator below indicates byte concatenation):
0x52 + 0x21 + key1.public_key + 0x21 + key2.public_key + 0x21 + key3.public_key + 0x53 + 0xae
From that, you compute:
redeem_script_hash = ripemd160(sha256(redeem_script))
Then, you can construct the raw output locking_script
:
OP_HASH160 OP_DATA_20 redeem_script_hash OP_EQUAL
by again concatenating the raw bytes:
0xa9 + 0x14 + redeem_script_hash + 0x87
That's it!
You can either use the raw bytes directly if you're constructing a raw transaction yourself, or encode it into a Bitcoin address so other wallets can pay into it.
When time comes to spend, the input script will be of the form:
OP_0 OP_DATA_71 key1.signature OP_DATA_71 key2.signature OP_PUSHDATA1 105 redeem_script
and its raw bytes will be:
0x00 + 0x47 + key1.signature + 0x47 + key2.signature + 0x4c + 0x69 + redeem_script
Here's is a great reference for P2SH & multisig: https://btcinformation.org/en/developer-guide#multisig