diff --git a/js/Redemption.js b/js/Redemption.js index 9389e2c..eac1fc4 100644 --- a/js/Redemption.js +++ b/js/Redemption.js @@ -16,7 +16,7 @@ export const redemptionKeyToAddress = (module, redemptionKey, magic) => { if (redemptionKey.length !== REDEMPTION_PRIVATE_KEY_SIZE) { return false; } - const bufkey = newArray(module, seed); + const bufkey = newArray(module, redemptionKey); const bufaddr = newArray0(module, 1024); const rs = module.redemption_private_to_address(bufkey, magic, bufaddr); let result = copyArray(module, bufaddr, rs); @@ -37,6 +37,8 @@ export const createRedemptionTransaction = (module, redemptionKey, input, output if (redemptionKey.length !== REDEMPTION_PRIVATE_KEY_SIZE) { return false; } + redemptionKey = [...Buffer.from(redemptionKey)]; + input.id = Buffer.from(input.id).toString('hex'); const input_obj = { protocol_magic: magic, redemption_key: redemptionKey, input, output }; const input_str = JSON.stringify(input_obj); const input_array = iconv.encode(input_str, 'utf8'); diff --git a/js/tests/redemption.js b/js/tests/redemption.js new file mode 100644 index 0000000..c5fc816 --- /dev/null +++ b/js/tests/redemption.js @@ -0,0 +1,82 @@ +const expect = require('chai').expect; +const CardanoCrypto = require('../../dist/index.js'); +const bs58 = require('bs58'); +const cbor = require('cbor'); +const crc = require('crc'); + +const TEST_VECTORS = [ + { + redemptionKey: Buffer.from('qXQWDxI3JrlFRtC4SeQjeGzLbVXWBomYPbNO1Vfm1T4=', 'base64'), + expectedAddress: 'Ae2tdPwUPEZ1xZTLczMGYL5PhADi1nbFmESqS9vUuLkyUe1isQ77TRUE9NS', + txId: new Uint8Array([0xaa,0xd7,0x8a,0x13,0xb5,0x0a,0x01,0x4a,0x24,0x63,0x3c,0x7d,0x44,0xfd,0x8f,0x8d,0x18,0xf6,0x7b,0xbb,0x3f,0xa9,0xcb,0xce,0xdf,0x83,0x4a,0xc8,0x99,0x75,0x9d,0xcd]), + txOutIndex: 1, + coinValue: 12345678 + } +]; + +let mkTest = (i) => { + const { redemptionKey, expectedAddress, txId, txOutIndex, coinValue } = TEST_VECTORS[i]; + const cfg = CardanoCrypto.Config.defaultConfig(); + + describe('Test ' + i, function() { + before(async () => { + await CardanoCrypto.loadRustModule() + }); + + it('generates valid address', function() { + const a = CardanoCrypto.Redemption.redemptionKeyToAddress(redemptionKey, cfg.protocol_magic); + const [tagged, checksum] = cbor.decode(Buffer.from(a)); + expect(crc.crc32(tagged.value)).equal(checksum); + }); + + it('creates address matching expected', function() { + const a = CardanoCrypto.Redemption.redemptionKeyToAddress(redemptionKey, cfg.protocol_magic); + expect(bs58.encode(Buffer.from(a))).equal(expectedAddress) + }); + + it('generates valid transaction', function () { + const address = CardanoCrypto.Redemption.redemptionKeyToAddress(redemptionKey, cfg.protocol_magic); + const input = { id: txId, index: txOutIndex }; + const output = { address: bs58.encode(Buffer.from(address)), value: JSON.stringify(coinValue) }; + const { result: { cbor_encoded_tx } } = CardanoCrypto.Redemption.createRedemptionTransaction(redemptionKey, input, output, cfg.protocol_magic); + + // destruct result transaction + const [[resultInputs, resultOutputs, attributes], resultWitnesses] = cbor.decode(Buffer.from(cbor_encoded_tx)); + + // validate inputs + expect(resultInputs.length).equal(1); + expect(resultInputs[0].length).equal(2); + const [[intputType, inputTagged]] = resultInputs; + expect(intputType).equal(0); + const [inputId, inputIndex] = cbor.decode(inputTagged.value); + expect(inputIndex).equal(txOutIndex); + expect(inputId).deep.equal(txId); + + // validate outputs + expect(resultInputs.length).equal(1); + expect(resultInputs[0].length).equal(2); + const [[outputAddress, outputValue]] = resultOutputs; + expect(cbor.encode(outputAddress)).deep.equal(address); + expect(outputValue).equal(coinValue); + + // validate witness + expect(resultWitnesses.length).equal(1); + expect(resultWitnesses[0].length).equal(2); + const [[witnessType, witnessTagged]] = resultWitnesses; + expect(witnessType).equal(2); + const [witnessPub, witnessSign] = cbor.decode(witnessTagged.value); + + // TODO: expecting fake witness data - fix after implementing signing in Rust + expect(witnessPub.toString('hex')) + .equal('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); + expect(witnessSign.toString('hex')) + .equal('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); + }); + }); +}; + +describe('Test redemption', function() { + for (let i = 0; i < TEST_VECTORS.length; i++) { + mkTest(i); + } +}); diff --git a/package.json b/package.json index aec49cd..82d4369 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,10 @@ "mocha": "5.0.2", "nodemon": "1.17.1", "wasm-loader": "1.3.0", - "webpack": "^3.11.0" + "webpack": "^3.11.0", + "bs58": "4.0.1", + "cbor": "4.1.4", + "crc": "3.8.0" }, "peerDependencies": { "bip39": "2.3.0" diff --git a/wallet-wasm/src/lib.rs b/wallet-wasm/src/lib.rs index b68805b..0882573 100644 --- a/wallet-wasm/src/lib.rs +++ b/wallet-wasm/src/lib.rs @@ -1269,9 +1269,9 @@ pub extern "C" fn redemption_private_to_address( let pub_key = priv_key.public(); let addr_type = address::AddrType::ATRedeem; let addr_data = address::SpendingData::RedeemASD(pub_key.clone()); - let magic = cardano::config::ProtocolMagic::new(protocol_magic); + let magic = cardano::config::ProtocolMagic::from(protocol_magic); let addr_attr = address::Attributes::new_bootstrap_era(None,magic.into()); - let address = address::ExtendedAddr::new(addr_type, addr_data, addr_attr)); + let address = address::ExtendedAddr::new(addr_type, addr_data, addr_attr); let address_bytes = cbor!(address).unwrap(); unsafe { write_data(&address_bytes, out) } return address_bytes.len() as u32; @@ -1280,7 +1280,7 @@ pub extern "C" fn redemption_private_to_address( #[derive(Serialize, Deserialize, Debug)] struct WalletRedeemInput { protocol_magic: cardano::config::ProtocolMagic, - redemption_key: redeem::PrivateKey, + redemption_key: [u8; redeem::PRIVATEKEY_SIZE], // hex input: TxIn, output: TxOut, } @@ -1297,29 +1297,48 @@ pub extern "C" fn xwallet_redeem( output_ptr: *mut c_uchar, ) -> i32 { let data: WalletRedeemInput = input_json!(output_ptr, input_ptr, input_sz); - let &mut txbuilder = txbuild::TxBuilder::new(); + let mut txbuilder = txbuild::TxBuilder::new(); txbuilder.add_input(&data.input.convert(), data.output.value.0); txbuilder.add_output_value(&data.output.convert()); let tx: tx::Tx = jrpc_try!( output_ptr, txbuilder.make_tx() - .map_err(|e| JsValue::from_str(&format! {"{:?}", e})) ); - let witness = tx::TxInWitness::redeem( - data.protocol_magic, &data.redemption_key, &tx.0.id()); - let &mut finalized = txbuild::TxFinalized::new(tx); + print!("Tx: {}", tx); + let redemption_key = jrpc_try!( + output_ptr, + redeem::PrivateKey::from_slice(&data.redemption_key) + ); + print!("Key: {}", redemption_key); + let witness = jrpc_try!( + output_ptr, + create_redemption_witness(data.protocol_magic, &redemption_key, &tx.id()) + ); + let mut finalized = txbuild::TxFinalized::new(tx); jrpc_try!( output_ptr, finalized.add_witness(witness) - .map_err(|e| JsValue::from_str(&format! {"{:?}", e})) ); let txaux: tx::TxAux = jrpc_try!( output_ptr, finalized.make_txaux() - .map_err(|e| JsValue::from_str(&format! {"{:?}", e})) ); let cbor = jrpc_try!(output_ptr, cbor!(&txaux)); jrpc_ok!(output_ptr, WalletRedeemOutput { cbor_encoded_tx: cbor }) } + +fn create_redemption_witness( + protocol_magic: cardano::config::ProtocolMagic, + key: &redeem::PrivateKey, + txid: &tx::TxId, +) -> redeem::Result { + // TODO: actual implementation + let s32 = (0..64).map(|_| "f").collect::(); + let s64 = (0..128).map(|_| "f").collect::(); + let pk = redeem::PublicKey::from_hex(&s32); + let sg = redeem::Signature::from_hex(&s64); + return pk.and_then(|k| sg.map(|s| (k, s))) + .map(|(k,s)| tx::TxInWitness::RedeemWitness(k, s)); +} \ No newline at end of file