From bf644a10c22e232ca4d0b16cb9308a12cfc62a48 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Fri, 18 Jan 2019 17:22:20 +0300 Subject: [PATCH 1/6] Implemented `redemption_private_to_address` to `lib.rs` for old API --- wallet-wasm/src/lib.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/wallet-wasm/src/lib.rs b/wallet-wasm/src/lib.rs index c17465d..14edcd4 100644 --- a/wallet-wasm/src/lib.rs +++ b/wallet-wasm/src/lib.rs @@ -25,6 +25,7 @@ use self::cardano::wallet::{ scheme::{SelectionPolicy, Wallet}, }; use self::cardano::{coin, fee, tx, txutils, util::hex}; +use self::cardano::{redeem, txbuild}; use self::cardano::util::try_from_slice::TryFromSlice; @@ -1254,3 +1255,40 @@ pub extern "C" fn decrypt_with_password( -1 } } + +#[no_mangle] +pub extern "C" fn redemption_private_to_address( + private_ptr: *const c_uchar, + protocol_magic: u32, + out: *mut c_uchar, +) -> u32 { + let priv_key = unsafe { + let slice: &[u8] = std::slice::from_raw_parts(private_ptr, redeem::PRIVATEKEY_SIZE); + redeem::PrivateKey::from_slice(slice).unwrap() + }; + 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 addr_attr = address::Attributes::new_bootstrap_era(None,magic.into()); + 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; +} + +#[derive(Serialize, Deserialize, Debug)] +struct WalletRedeemInput { + redemption_key: redeem::PrivateKey, + input: TxIn, + output: TxOut, +} + +#[no_mangle] +pub extern "C" fn xwallet_redeem( + input_ptr: *const c_uchar, + input_sz: usize, + output_ptr: *mut c_uchar, +) -> i32 { + +} From 65214605dc600175ad6848991b47ab3019ae3dfd Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Fri, 18 Jan 2019 18:01:22 +0300 Subject: [PATCH 2/6] Implemented `xwallet_redeem` in old API `lib.rs` --- wallet-wasm/src/lib.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/wallet-wasm/src/lib.rs b/wallet-wasm/src/lib.rs index 14edcd4..b3e0e08 100644 --- a/wallet-wasm/src/lib.rs +++ b/wallet-wasm/src/lib.rs @@ -1279,16 +1279,44 @@ pub extern "C" fn redemption_private_to_address( #[derive(Serialize, Deserialize, Debug)] struct WalletRedeemInput { + protocol_magic: u32, redemption_key: redeem::PrivateKey, input: TxIn, output: TxOut, } +#[derive(Serialize, Deserialize, Debug)] +struct WalletRedeemOutput { + cbor_encoded_tx: Vec, +} + #[no_mangle] pub extern "C" fn xwallet_redeem( input_ptr: *const c_uchar, input_sz: usize, output_ptr: *mut c_uchar, ) -> i32 { - + let data: WalletRedeemInput = input_json!(output_ptr, input_ptr, input_sz); + 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: result::Result = txbuilder.make_tx() + .map_err(|e| JsValue::from_str(&format! {"{:?}", e})); + let txaux: tx::TxAux = jrpc_try!(output_ptr, tx.and_then(|tx| { + let magic = cardano::config::ProtocolMagic::new(data.protocol_magic); + let witness = tx::TxInWitness::redeem(magic, &data.redemption_key, &tx.0.id()); + let &mut finalized = txbuild::TxFinalized::new(tx); + let witness_result: result::Result<(), JsValue> = finalized + .add_witness(witness) + .map_err(|e| JsValue::from_str(&format! {"{:?}", e})); + return witness_result.and_then(|| { + return 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 + }) } From 1ae6d33359441bf7245fa340a7813890edf6d2af Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Fri, 18 Jan 2019 18:25:42 +0300 Subject: [PATCH 3/6] Fixed some code in redemption functions in old API --- wallet-wasm/src/lib.rs | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/wallet-wasm/src/lib.rs b/wallet-wasm/src/lib.rs index b3e0e08..b68805b 100644 --- a/wallet-wasm/src/lib.rs +++ b/wallet-wasm/src/lib.rs @@ -1279,7 +1279,7 @@ pub extern "C" fn redemption_private_to_address( #[derive(Serialize, Deserialize, Debug)] struct WalletRedeemInput { - protocol_magic: u32, + protocol_magic: cardano::config::ProtocolMagic, redemption_key: redeem::PrivateKey, input: TxIn, output: TxOut, @@ -1300,21 +1300,24 @@ pub extern "C" fn xwallet_redeem( 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: result::Result = txbuilder.make_tx() - .map_err(|e| JsValue::from_str(&format! {"{:?}", e})); - let txaux: tx::TxAux = jrpc_try!(output_ptr, tx.and_then(|tx| { - let magic = cardano::config::ProtocolMagic::new(data.protocol_magic); - let witness = tx::TxInWitness::redeem(magic, &data.redemption_key, &tx.0.id()); - let &mut finalized = txbuild::TxFinalized::new(tx); - let witness_result: result::Result<(), JsValue> = finalized - .add_witness(witness) - .map_err(|e| JsValue::from_str(&format! {"{:?}", e})); - return witness_result.and_then(|| { - return finalized - .make_txaux() - .map_err(|e| JsValue::from_str(&format! {"{:?}", e})); - }); - })); + 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); + 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 From 42d26d222a3363420e853d5ccfe6ff9e4a3a5cd3 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Fri, 18 Jan 2019 20:37:56 +0300 Subject: [PATCH 4/6] Implemented `Redemption.js` connectors for new redemption functions in old API --- js/Redemption.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ js/index.js | 2 ++ 2 files changed, 62 insertions(+) create mode 100644 js/Redemption.js diff --git a/js/Redemption.js b/js/Redemption.js new file mode 100644 index 0000000..9389e2c --- /dev/null +++ b/js/Redemption.js @@ -0,0 +1,60 @@ +import iconv from 'iconv-lite'; +import RustModule from './RustModule'; +import { newArray, newArray0, copyArray } from './utils/arrays'; +import { apply } from './utils/functions'; + +export const REDEMPTION_PRIVATE_KEY_SIZE = 32; +const MAX_OUTPUT_SIZE = 4096; + +/** + * @param module - the WASM module that is used for crypto operations + * @param redemptionKey - the private redemption key, needs to be {@link REDEMPTION_PRIVATE_KEY_SIZE} + * @param magic - protocol magic integer + * @returns {*} - returns false if the seed is not of the valid length, or returns the redemption address + */ +export const redemptionKeyToAddress = (module, redemptionKey, magic) => { + if (redemptionKey.length !== REDEMPTION_PRIVATE_KEY_SIZE) { + return false; + } + const bufkey = newArray(module, seed); + const bufaddr = newArray0(module, 1024); + const rs = module.redemption_private_to_address(bufkey, magic, bufaddr); + let result = copyArray(module, bufaddr, rs); + module.dealloc(bufkey); + module.dealloc(bufaddr); + return result; +}; + +/** + * @param module - the WASM module that is used for crypto operations + * @param redemptionKey - the private redemption key, needs to be {@link REDEMPTION_PRIVATE_KEY_SIZE} + * @param input - single input as: { id, index } + * @param output - single output as: { address, value } + * @param magic - protocol magic integer + * @returns {*} - returns false if the seed is not of the valid length, or returns the response as: { cbor_encoded_tx } + */ +export const createRedemptionTransaction = (module, redemptionKey, input, output, magic) => { + if (redemptionKey.length !== REDEMPTION_PRIVATE_KEY_SIZE) { + return false; + } + 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'); + + const bufinput = newArray(module, input_array); + const bufoutput = newArray0(module, MAX_OUTPUT_SIZE); + + let rsz = module.xwallet_redeem(bufinput, input_array.length, bufoutput); + let output_array = copyArray(module, bufoutput, rsz); + + module.dealloc(bufoutput); + module.dealloc(bufinput); + + let output_str = iconv.decode(Buffer.from(output_array), 'utf8'); + return JSON.parse(output_str); +}; + +export default { + redemptionKeyToAddress: apply(redemptionKeyToAddress, RustModule), + createRedemptionTransaction: apply(createRedemptionTransaction, RustModule), +}; diff --git a/js/index.js b/js/index.js index 9efdb4e..eb011a4 100644 --- a/js/index.js +++ b/js/index.js @@ -6,6 +6,7 @@ import Payload from './Payload.js'; import Tx from './Tx.js'; import Config from './Config.js'; import Wallet from './Wallet.js'; +import Redemption from './Redemption.js'; import RandomAddressChecker from './RandomAddressChecker'; import PasswordProtect from './PasswordProtect'; @@ -19,6 +20,7 @@ module.exports = { RandomAddressChecker, HdWallet, Wallet, + Redemption, Config, PasswordProtect, }; From 94606adcf71c8bbca8473cdb3975cec5929694a9 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 22 Jan 2019 03:21:34 +0300 Subject: [PATCH 5/6] Implemented js-tests for new redemption connectors, and fixed errors --- js/Redemption.js | 4 ++- js/tests/redemption.js | 82 ++++++++++++++++++++++++++++++++++++++++++ package.json | 5 ++- wallet-wasm/src/lib.rs | 39 ++++++++++++++------ 4 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 js/tests/redemption.js 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 From a5f4a2e558c815c1d3392e968e442f44c4f18076 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Tue, 22 Jan 2019 03:30:02 +0300 Subject: [PATCH 6/6] Simplified `redemption_private_to_address` function --- wallet-wasm/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/wallet-wasm/src/lib.rs b/wallet-wasm/src/lib.rs index 0882573..1abd4e1 100644 --- a/wallet-wasm/src/lib.rs +++ b/wallet-wasm/src/lib.rs @@ -1267,11 +1267,8 @@ pub extern "C" fn redemption_private_to_address( redeem::PrivateKey::from_slice(slice).unwrap() }; 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::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) = tx::redeem_pubkey_to_txid(&pub_key, magic); let address_bytes = cbor!(address).unwrap(); unsafe { write_data(&address_bytes, out) } return address_bytes.len() as u32;