Bitcoin Exs PDF

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 15

Bitcoin

Ejemplos con bitcoinj


josé a. mañas

25.2.2017

1 Introducción
Buscando en Internet se pueden encontrar muchos ejemplos para programar aplicaciones que
hagan uso de las tecnologías bitcoin y blockchain.

Esta nota solamente busca concretar algunos ejemplos sencillos usando la librería bitcoinj, un
entorno de desarrollo para java.

 https://bitcoinj.github.io/

Los ejemplos se han realizado y probado bajo la versión

 0.14.3 del 14.1.2017

La librería deja de ser perfecta

 es incompleta
 tiene bugs
 la documentación no siempre está al día
 cambia con cierta frecuencia (desarrollo activo)

por lo que los programas que se presentan puede que haya que afinarlos.

2 Contexto
Todos los ejemplos están parametrizados para trabajar sobre la red oficial de bitcoin (manejando
monedas convertibles a euros)
NetworkParameters netParams = MainNetParams.get();

o sobre la red de pruebas (dinero ficticio)


NetworkParameters netParams = TestNet3Params.get();

Las transacciones sobre la red oficial se pueden seguir en

https://blockchain.info/

y las de la red de pruebas en

http://tbtc.blockr.io/

y se pueden conseguir monedas de pruebas en varios faucets, como por ejemplo


http://tpfaucet.appspot.com/

Por favor, siga las reglas de la comunidad.

3 Logging
Para ver trazas de qué está haciendo la aplicación, hay que activar un logger.

Descargamos la colección de loggers de

https://www.slf4j.org/

lo descomprimimos
./slf4j-1.7.22/slf4j-1.7.22/slf4j-simple-1.7.22.jar

y lo colocamos como librería del Proyecto

4 Claves y direcciones
El primer programa crea una clave aleatoria e imprime sus partes pública, privada y su dirección
en la red bitcoin:

void anAddress(NetworkParameters netParams) {


ECKey key = new ECKey();

System.out.println("key:");
System.out.println(" pub: " + key.getPublicKeyAsHex());
System.out.println(" sec: " + key.getPrivateKeyAsHex());
System.out.println(" address: " + key.toAddress(netParams));
}

4.1 Ejercicio
Se propone crear una dirección de vanidoso (vanity address). Ver
https://en.wikipedia.org/wiki/Vanity_plate

Concretamente, elija 4 letras (por ejemplo, “pepe”) y cree una dirección que empiece así

1pepe…

ej. 1pepe9NvM1cWYweJSPt62cJuc19h29Tb3R

Debido a la irreversibilidad de las funciones hash, no hay más remedio que ir generando y
probando claves hasta obtener una que cumpla el patrón deseado.

Ver discusión en

https://en.bitcoin.it/wiki/Vanitygen
5 Wallet
Un programa útil puede ser el que muestra una cartera, almacenada en un fichero, creándola si es
la primera vez:

String DIRECTORY = “C:/Users/jose/…”;

void aWallet(NetworkParameters netParams, String prefix) {


File directory = new File(DIRECTORY);
WalletAppKit kit = new WalletAppKit(netParams, directory, prefix);
kit.startAsync();
kit.awaitRunning();

Wallet wallet = kit.wallet();


System.out.println("wallet: " + wallet);

DeterministicSeed seed = wallet.getKeyChainSeed();


System.out.println("seed: " + seed.toHexString());
System.out.println(" " + Utils.join(seed.getMnemonicCode()));

ECKey key0 = wallet.currentReceiveKey();


System.out.println("receiving address: " + key0.toAddress(netParams));

System.out.println("imported keys: ");


for (ECKey key : wallet.getImportedKeys())
System.out.println(" key: " + key.toAddress(netParams));

System.out.println("issued keys: ");


for (ECKey key : wallet.getIssuedReceiveKeys())
System.out.println(" key: " + key.toAddress(netParams));

System.out.println("balance: " + wallet.getBalance().toFriendlyString());


}
6 Pago
El siguiente método manda bitcoins a la dirección que le digamos.

La cantidad a enviar se recoge en objetos de tipo Coin. Vea un par de ejemplos

Coin System.out.println(coin.toFriendlyString())
coin = Coin.parseCoin("0.1")); 0.10 BTC
coin = Coin.valueOf(0, 1)); 0.01 BTC
coin = Coin.valueOf(1000) 0.00001 BTC

Y el código para mandar a una cierta dirección:

void send(NetworkParameters netParams,


String prefix, Coin coin, Address address)
throws InsufficientMoneyException, ExecutionException,
InterruptedException {
WalletAppKit kit = new WalletAppKit(netParams, new File("."), prefix);
kit.startAsync();
kit.awaitRunning();

Wallet wallet = kit.wallet();


Wallet.SendResult result =
wallet.sendCoins(kit.peerGroup(), address, coin);
result.broadcastComplete.get();
}

Cuando el escenario no es tan simple, hay que trabajar la transacción concreta. El siguiente
ejemplo hace lo mismo preparando explícitamente la salida de la transacción:
void send(NetworkParameters netParams,
String prefix, Coin coin1, Address address)
throws InsufficientMoneyException, ExecutionException,
InterruptedException {
WalletAppKit kit = new WalletAppKit(netParams, new File("."), prefix);
kit.startAsync();
kit.awaitRunning();

Transaction transaction = new Transaction(netParams);


transaction.addOutput(coin1, address);
SendRequest request = SendRequest.forTx(transaction);

Wallet wallet = kit.wallet();


wallet.completeTx(request);
wallet.commitTx(request.tx);
ListenableFuture<Transaction> future =
kit.peerGroup().broadcastTransaction(request.tx).future();
future.get();
}

6.1 Ejercicio
Dada una cantidad de monedas X, repártala en N partes y envíe una parte a cada destinatario de
una lista de N direcciones. Hay que hacer una única transacción para acotar las comisiones
pagadas a los mineros.

7 Firmas N de M
Se trata de hacer una transacción que involucra a M partes y solamente puede ser cobrada cuando
N receptores están de acuerdo en quién debe cobrarla.

Por ejemplo, una firma 2 de 3. Una dirección A genera una transacción para 3 partes, B1, B2 y B3.
Para que C pueda cobrarla, necesita las firmas de 2 elementos del conjunto {B1, B2, B3}.

En este caso hay que trabajar con claves públicas, no nos basta tener direcciones. A partir de las
claves públicas en hexadecimal, podemos obtener la codificación en bytes:

List<String> pubKeyList = new ArrayList<>();

List<ECKey> keyList = new ArrayList<>();


for (String pubKeyHex : pubKeyList) {
byte[] pubKeyBytes = Utils.parseAsHexOrBase58(pubKeyHex);
ECKey key = ECKey.fromPublicOnly(pubKeyBytes);
keyList.add(key);
}
Primero hay que generar una transacción de A a {B1, B2, B3}, con un script como este
2 <pubKey1> <pubKey2> <pubKey3> 3 CHECKMULTISIG

y luego una transacción para C con las suficientes firmas y un script

0 <sig1> <sig2>

Todo junto para validación

0 <sig1> <sig2> 2 <pubKey1> <pubKey2> <pubKey3> 3 CHECKMULTISIG

Ver https://en.bitcoin.it/wiki/Transaction

Es importante destacar que el orden de las firmas <sig_i> debe respetar el orden de las claves públicas
<pubKey_i>.

7.1 Transacciones
El que paga genera una transacción T1, firmada por él.

Necesitamos una transacción intermedia T2, firmada por 2, para que el dinero llegue al destino
final F.
7.2 Generación de T1
Hay que ajustar el script de la transacción para que sea del tipo multifirma: se requerirán N firmas
de una lista de claves:

private static Transaction sendNM(NetworkParameters netParams,


String prefix, Coin coin, int N, List<ECKey> keyList)
throws InsufficientMoneyException, ExecutionException,
InterruptedException {
WalletAppKit kit = new WalletAppKit(netParams, new File("."), prefix);
kit.startAsync();
kit.awaitRunning();

Transaction T1 = new Transaction(netParams);


Script script = ScriptBuilder.createMultiSigOutputScript(N, keyList);
T1.addOutput(coin, script);
SendRequest request = SendRequest.forTx(T1);

Wallet wallet = kit.wallet();


wallet.completeTx(request);
wallet.commitTx(request.tx);

PeerGroup peerGroup = kit.peerGroup();


ListenableFuture<Transaction> future =
peerGroup.broadcastTransaction(request.tx).future();
future.get();
return T1;
}

Es importante apuntarse la transacción porque habrá que mandársela a los posibles signatarios
para que la firmen. También suele ser útil apuntarse su hash para poder seguirla en la blockchain.

Transaction T1 = sendNM(...);
String T1Hash = T1.getHashAsString();
System.out.println("T1: " + T1Hash);

saveToFile(T1);

7.3 Transacción a firmar: T2


La transacción T2 tiene que ser firmada por N participantes, que están de acuerdo en transferir la
cantidad prometida a un cierto destinatario identificado por su dirección bitcoin

El siguiente código prepara una transacción T2 que


1. toma la salida de la T1 que llega,
2. le resta una comisión (fee),
3. genera las firmas y
4. se la remita al destinatario final:

Transaction T1 = getTransaction(netParams, T1Hash);


TransactionOutput output1 = getMultiSigOutput(T1);

Coin fee2 = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(2);


Coin value2 = output1.getValue().subtract(fee2);

Transaction T2 = new Transaction(netParams);


TransactionInput input2 = T2.addInput(output1);
Address finalAddress = Address.fromBase58(netParams, finalReceiver);
T2.addOutput(value2, finalAddress);

Transaction.SigHash type = Transaction.SigHash.ALL;


Script scriptPubKey = output1.getScriptPubKey();
List<ECKey> pubKeyList = scriptPubKey.getPubKeys();
List<TransactionSignature> signatureList = new ArrayList<>();
for (int i = 0; i < pubKeyList.size(); i++) {
ECKey key = findKey(pubKeyList.get(i));
if (key == null)
continue; // no se firmar con esa clave
Sha256Hash sighash = T2.hashForSignature(0, scriptPubKey, type, false);
ECKey.ECDSASignature ecdsaSignature = key.sign(sighash);
TransactionSignature signature =
new TransactionSignature(ecdsaSignature, type, false);
signatureList.add(signature);
}

Script inputScript = ScriptBuilder.createMultiSigInputScript(signatureList);


input2.setScriptSig(inputScript);

input2.verify(output1);

WalletAppKit kit = …
PeerGroup peerGroup = kit.peerGroup();
ListenableFuture<Transaction> future = peerGroup.broadcastTransaction(T2).future();
future.get();
La transacción entrante la podemos recuperar del fichero que generamos en la sección previa

Y hay que seleccionar, de todas las salidas de la transacción, la que nos interesa para multi-firma

TransactionOutput getMultiSigOutput(Transaction transaction) {


for (TransactionOutput output : transaction.getOutputs()) {
Script script = output.getScriptPubKey();
if (script.isSentToMultiSig())
return output;
}
return null;
}

7.4 Firma uno a uno


De T1 podemos extraer la salida multifirma y los signatarios:

Transaction T1 = …
TransactionOutput output1 = getMultiSigOutput(T1);
Script scriptPubKey1 = output1.getScriptPubKey();
List<ECKey> pubKeyList = scriptPubKey1.getPubKeys();

De esas claves, tendremos acceso a alguna clave privada que nos permita firmar. Sabiendo la clave
privada, se firma

ECKey key = …
Transaction.SigHash type = Transaction.SigHash.ALL;
Sha256Hash sighash = T2.hashForSignature(0, scriptPubKey1, type, false);
ECKey.ECDSASignature ecdsaSignature = key.sign(sighash);

Para encontrar la clave de firma, hay que buscar en la wallet. Este código cada uno se lo organiza a
su manera, siendo la forma de operar como sigue:
WalletAppKit kit =
new WalletAppKit(netParams, new File(DIRECTORY), PREFIX);
kit.startAsync();
kit.awaitRunning();
Wallet wallet = kit.wallet();
byte[] pubKeyBytes = Utils.parseAsHexOrBase58(PUBKEYHEX);
ECKey key = wallet.findKeyFromPubKey(pubKeyBytes);
7.5 Cierre
Cuando ya tenemos suficientes firmas, bien generadas por nosotros mismos, bien recibidas de un
signatario, nos quedamos con la N que hacen falta y generamos la transacción final para que el
destinatario reciba el dinero

List<TransactionSignature> signatureList = new ArrayList<>();


signatureList.add(...);
signatureList.add(...);

Script inputScript =
ScriptBuilder.createMultiSigInputScript(signatureList);
input2.setScriptSig(inputScript);

Context context = new Context(netParams);


WalletAppKit kit = new WalletAppKit(context, new File(HOME), "pp");
kit.startAsync();
kit.awaitRunning();

input2.verify(output1);

PeerGroup peerGroup = kit.peerGroup();


ListenableFuture<Transaction> future =
peerGroup.broadcastTransaction(T2).future();
future.get();

7.6 Ejercicio
Aplique el código anterior a un contrato de compra-venta supervisado por un tercero. Es decir, es
un escenario que requiere la firma de 2 de 3.

 C es el cliente
 V es el vendedor
 J es el juez

1. C manda una cantidad X a tres participantes: C, V y J


2. V manda el producto

Si C está de acuerdo, C firma y le pasa la firma a V. V firma y se manda la cantidad a sí mismo.

Si C no está de acuerdo, le reclama una devolución a V. Si V está de acuerdo, recibe el producto y


firma. Le pasa la firma a C que lo firma de nuevo y se manda la cantidad a sí mismo.

Si C y V no se ponen de acuerdo, entra J a juzgar quien tiene razón.

 Si piensa que C tiene razón, firma la transacción para C y se la pasa a C para que firme y
recupere su dinero.
 Si piensa que V tiene razón, firma la transacción para V y se la pasa a V para que firme y
cobre su producto.
7.7 Ejemplo
Generamos una transacción T1 para que firmen 2 de 3:

hash: f7d8b240f28fd012bcb2d42912a2767136944f9ec0a15aabfd4bae4f4221b039
output 0
value: 0.0250877 BTC
scriptPubKey:
 DUP
 HASH160
 PUSHDATA(20)[fcc7cb46725e438d2c4f2689d84865f071d373bc]
 EQUALVERIFY
 CHECKSIG
output 1
value: 0.0123 BTC
scriptPubKey:
 2
 PUSHDATA(33)[0328b759cb1775dd3465c5ac925999e5ed2ed9886acbac88cc8ba82ae8b
b59d48c]
 PUSHDATA(33)[02b4a45b26cd6e101283270e247875afeb71593754e0eb1dd9cc109c40a
c395dd2]
 PUSHDATA(33)[03258f48893c359d0f573e8ece07dee26e4afdccbf42eee61c67f802d58e
d2e597]
 3 CHECKMULTISIG

Generamos la parte a firmar (el cuerpo) de la T2:

hash: 1b13a553ab4f656fe3629b6012e6309b160622dc72f19a8e12849df475a9c56c
output 0
value: 0.0122 BTC
scriptPubKey:
 DUP
 HASH160
 PUSHDATA(20)[bded386a1395fa69d531719a470c43408851f926]
 EQUALVERIFY
 CHECKSIG

Firmamos un par de veces y lo aportamos a la transacción, resultando en un conjunto que


enviamos a blockchain:
hash: 7890353f52404fee2fb55c0ac4a6f220ce76452b4bf2b047ad8ebf8ce5c8d4d4
output 0
value: 0.0122 BTC
scriptPubKey:
 DUP
 HASH160
 PUSHDATA(20)[bded386a1395fa69d531719a470c43408851f926]
 EQUALVERIFY
 CHECKSIG

En blockchain aparecen las dos transacciones.

La primera, T1 (f7d8b240f28fd012bcb2d42912a2767136944f9ec0a15aabfd4bae4f4221b039):

… y la segunda: T2 (7890353f52404fee2fb55c0ac4a6f220ce76452b4bf2b047ad8ebf8ce5c8d4d4):
8 Métodos auxiliares para intercambiar transacciones y firmas
8.1 Transacciones

void saveToFile(Transaction transaction)


throws IOException {
String pathname = transaction.getHashAsString() + ".tx";
File file = new File(pathname);
OutputStream stream = new FileOutputStream(file);
stream.write(transaction.bitcoinSerialize());
stream.close();
System.out.println("transaction T2: " + pathname);
}

Transaction retoreFromFile(NetworkParameters netParams, String transactionHash)


throws IOException {
File file = new File(transactionHash + ".tx");
int size = (int) file.length();
byte[] bytes = new byte[size];
InputStream stream = new FileInputStream(file);
stream.read(bytes);
stream.close();
return new Transaction(netParams, bytes);
}

8.2 Firmas

void saveToFile(Transaction transaction, ECKey.ECDSASignature signature, int position)


throws IOException {
String filename = String.format("%s_%d.%s",
transaction.getHashAsString(), position, "sig");
File file = new File(filename);
OutputStream stream = new FileOutputStream(file);
stream.write(signature.encodeToDER());
stream.close();
}
ECKey.ECDSASignature restoreFromFile(Transaction transaction, int position)
throws IOException {
String filename = String.format("%s_%d.%s",
transaction.getHashAsString(), position, "sig");
File file = new File(filename);
int size = (int) file.length();
byte[] bytes = new byte[size];
InputStream stream = new FileInputStream(file);
stream.read(bytes);
stream.close();
return ECKey.ECDSASignature.decodeFromDER(bytes);
}

También podría gustarte