Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions js/Redemption.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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, redemptionKey);
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;
}
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');

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),
};
2 changes: 2 additions & 0 deletions js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -19,6 +20,7 @@ module.exports = {
RandomAddressChecker,
HdWallet,
Wallet,
Redemption,
Config,
PasswordProtect,
};
82 changes: 82 additions & 0 deletions js/tests/redemption.js
Original file line number Diff line number Diff line change
@@ -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);
}
});
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
85 changes: 85 additions & 0 deletions wallet-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1254,3 +1255,87 @@ 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 magic = cardano::config::ProtocolMagic::from(protocol_magic);
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;
}

#[derive(Serialize, Deserialize, Debug)]
struct WalletRedeemInput {
protocol_magic: cardano::config::ProtocolMagic,
redemption_key: [u8; redeem::PRIVATEKEY_SIZE], // hex
input: TxIn,
output: TxOut,
}

#[derive(Serialize, Deserialize, Debug)]
struct WalletRedeemOutput {
cbor_encoded_tx: Vec<u8>,
}

#[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: tx::Tx = jrpc_try!(
output_ptr,
txbuilder.make_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)
);
let txaux: tx::TxAux = jrpc_try!(
output_ptr,
finalized.make_txaux()
);
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<tx::TxInWitness> {
// TODO: actual implementation
let s32 = (0..64).map(|_| "f").collect::<String>();
let s64 = (0..128).map(|_| "f").collect::<String>();
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));
}