Skip to content

Commit 7be4050

Browse files
authored
Merge pull request #44 from vantuz-subhuman/redemption-to-old-api
Redemption features to old API
2 parents 7c7b460 + a5f4a2e commit 7be4050

File tree

5 files changed

+235
-1
lines changed

5 files changed

+235
-1
lines changed

js/Redemption.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import iconv from 'iconv-lite';
2+
import RustModule from './RustModule';
3+
import { newArray, newArray0, copyArray } from './utils/arrays';
4+
import { apply } from './utils/functions';
5+
6+
export const REDEMPTION_PRIVATE_KEY_SIZE = 32;
7+
const MAX_OUTPUT_SIZE = 4096;
8+
9+
/**
10+
* @param module - the WASM module that is used for crypto operations
11+
* @param redemptionKey - the private redemption key, needs to be {@link REDEMPTION_PRIVATE_KEY_SIZE}
12+
* @param magic - protocol magic integer
13+
* @returns {*} - returns false if the seed is not of the valid length, or returns the redemption address
14+
*/
15+
export const redemptionKeyToAddress = (module, redemptionKey, magic) => {
16+
if (redemptionKey.length !== REDEMPTION_PRIVATE_KEY_SIZE) {
17+
return false;
18+
}
19+
const bufkey = newArray(module, redemptionKey);
20+
const bufaddr = newArray0(module, 1024);
21+
const rs = module.redemption_private_to_address(bufkey, magic, bufaddr);
22+
let result = copyArray(module, bufaddr, rs);
23+
module.dealloc(bufkey);
24+
module.dealloc(bufaddr);
25+
return result;
26+
};
27+
28+
/**
29+
* @param module - the WASM module that is used for crypto operations
30+
* @param redemptionKey - the private redemption key, needs to be {@link REDEMPTION_PRIVATE_KEY_SIZE}
31+
* @param input - single input as: { id, index }
32+
* @param output - single output as: { address, value }
33+
* @param magic - protocol magic integer
34+
* @returns {*} - returns false if the seed is not of the valid length, or returns the response as: { cbor_encoded_tx }
35+
*/
36+
export const createRedemptionTransaction = (module, redemptionKey, input, output, magic) => {
37+
if (redemptionKey.length !== REDEMPTION_PRIVATE_KEY_SIZE) {
38+
return false;
39+
}
40+
redemptionKey = [...Buffer.from(redemptionKey)];
41+
input.id = Buffer.from(input.id).toString('hex');
42+
const input_obj = { protocol_magic: magic, redemption_key: redemptionKey, input, output };
43+
const input_str = JSON.stringify(input_obj);
44+
const input_array = iconv.encode(input_str, 'utf8');
45+
46+
const bufinput = newArray(module, input_array);
47+
const bufoutput = newArray0(module, MAX_OUTPUT_SIZE);
48+
49+
let rsz = module.xwallet_redeem(bufinput, input_array.length, bufoutput);
50+
let output_array = copyArray(module, bufoutput, rsz);
51+
52+
module.dealloc(bufoutput);
53+
module.dealloc(bufinput);
54+
55+
let output_str = iconv.decode(Buffer.from(output_array), 'utf8');
56+
return JSON.parse(output_str);
57+
};
58+
59+
export default {
60+
redemptionKeyToAddress: apply(redemptionKeyToAddress, RustModule),
61+
createRedemptionTransaction: apply(createRedemptionTransaction, RustModule),
62+
};

js/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Payload from './Payload.js';
66
import Tx from './Tx.js';
77
import Config from './Config.js';
88
import Wallet from './Wallet.js';
9+
import Redemption from './Redemption.js';
910
import RandomAddressChecker from './RandomAddressChecker';
1011
import PasswordProtect from './PasswordProtect';
1112

@@ -19,6 +20,7 @@ module.exports = {
1920
RandomAddressChecker,
2021
HdWallet,
2122
Wallet,
23+
Redemption,
2224
Config,
2325
PasswordProtect,
2426
};

js/tests/redemption.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
const expect = require('chai').expect;
2+
const CardanoCrypto = require('../../dist/index.js');
3+
const bs58 = require('bs58');
4+
const cbor = require('cbor');
5+
const crc = require('crc');
6+
7+
const TEST_VECTORS = [
8+
{
9+
redemptionKey: Buffer.from('qXQWDxI3JrlFRtC4SeQjeGzLbVXWBomYPbNO1Vfm1T4=', 'base64'),
10+
expectedAddress: 'Ae2tdPwUPEZ1xZTLczMGYL5PhADi1nbFmESqS9vUuLkyUe1isQ77TRUE9NS',
11+
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]),
12+
txOutIndex: 1,
13+
coinValue: 12345678
14+
}
15+
];
16+
17+
let mkTest = (i) => {
18+
const { redemptionKey, expectedAddress, txId, txOutIndex, coinValue } = TEST_VECTORS[i];
19+
const cfg = CardanoCrypto.Config.defaultConfig();
20+
21+
describe('Test ' + i, function() {
22+
before(async () => {
23+
await CardanoCrypto.loadRustModule()
24+
});
25+
26+
it('generates valid address', function() {
27+
const a = CardanoCrypto.Redemption.redemptionKeyToAddress(redemptionKey, cfg.protocol_magic);
28+
const [tagged, checksum] = cbor.decode(Buffer.from(a));
29+
expect(crc.crc32(tagged.value)).equal(checksum);
30+
});
31+
32+
it('creates address matching expected', function() {
33+
const a = CardanoCrypto.Redemption.redemptionKeyToAddress(redemptionKey, cfg.protocol_magic);
34+
expect(bs58.encode(Buffer.from(a))).equal(expectedAddress)
35+
});
36+
37+
it('generates valid transaction', function () {
38+
const address = CardanoCrypto.Redemption.redemptionKeyToAddress(redemptionKey, cfg.protocol_magic);
39+
const input = { id: txId, index: txOutIndex };
40+
const output = { address: bs58.encode(Buffer.from(address)), value: JSON.stringify(coinValue) };
41+
const { result: { cbor_encoded_tx } } = CardanoCrypto.Redemption.createRedemptionTransaction(redemptionKey, input, output, cfg.protocol_magic);
42+
43+
// destruct result transaction
44+
const [[resultInputs, resultOutputs, attributes], resultWitnesses] = cbor.decode(Buffer.from(cbor_encoded_tx));
45+
46+
// validate inputs
47+
expect(resultInputs.length).equal(1);
48+
expect(resultInputs[0].length).equal(2);
49+
const [[intputType, inputTagged]] = resultInputs;
50+
expect(intputType).equal(0);
51+
const [inputId, inputIndex] = cbor.decode(inputTagged.value);
52+
expect(inputIndex).equal(txOutIndex);
53+
expect(inputId).deep.equal(txId);
54+
55+
// validate outputs
56+
expect(resultInputs.length).equal(1);
57+
expect(resultInputs[0].length).equal(2);
58+
const [[outputAddress, outputValue]] = resultOutputs;
59+
expect(cbor.encode(outputAddress)).deep.equal(address);
60+
expect(outputValue).equal(coinValue);
61+
62+
// validate witness
63+
expect(resultWitnesses.length).equal(1);
64+
expect(resultWitnesses[0].length).equal(2);
65+
const [[witnessType, witnessTagged]] = resultWitnesses;
66+
expect(witnessType).equal(2);
67+
const [witnessPub, witnessSign] = cbor.decode(witnessTagged.value);
68+
69+
// TODO: expecting fake witness data - fix after implementing signing in Rust
70+
expect(witnessPub.toString('hex'))
71+
.equal('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
72+
expect(witnessSign.toString('hex'))
73+
.equal('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
74+
});
75+
});
76+
};
77+
78+
describe('Test redemption', function() {
79+
for (let i = 0; i < TEST_VECTORS.length; i++) {
80+
mkTest(i);
81+
}
82+
});

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
"mocha": "5.0.2",
2525
"nodemon": "1.17.1",
2626
"wasm-loader": "1.3.0",
27-
"webpack": "^3.11.0"
27+
"webpack": "^3.11.0",
28+
"bs58": "4.0.1",
29+
"cbor": "4.1.4",
30+
"crc": "3.8.0"
2831
},
2932
"peerDependencies": {
3033
"bip39": "2.3.0"

wallet-wasm/src/lib.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use self::cardano::wallet::{
2525
scheme::{SelectionPolicy, Wallet},
2626
};
2727
use self::cardano::{coin, fee, tx, txutils, util::hex};
28+
use self::cardano::{redeem, txbuild};
2829

2930
use self::cardano::util::try_from_slice::TryFromSlice;
3031

@@ -1254,3 +1255,87 @@ pub extern "C" fn decrypt_with_password(
12541255
-1
12551256
}
12561257
}
1258+
1259+
#[no_mangle]
1260+
pub extern "C" fn redemption_private_to_address(
1261+
private_ptr: *const c_uchar,
1262+
protocol_magic: u32,
1263+
out: *mut c_uchar,
1264+
) -> u32 {
1265+
let priv_key = unsafe {
1266+
let slice: &[u8] = std::slice::from_raw_parts(private_ptr, redeem::PRIVATEKEY_SIZE);
1267+
redeem::PrivateKey::from_slice(slice).unwrap()
1268+
};
1269+
let pub_key = priv_key.public();
1270+
let magic = cardano::config::ProtocolMagic::from(protocol_magic);
1271+
let (_, address) = tx::redeem_pubkey_to_txid(&pub_key, magic);
1272+
let address_bytes = cbor!(address).unwrap();
1273+
unsafe { write_data(&address_bytes, out) }
1274+
return address_bytes.len() as u32;
1275+
}
1276+
1277+
#[derive(Serialize, Deserialize, Debug)]
1278+
struct WalletRedeemInput {
1279+
protocol_magic: cardano::config::ProtocolMagic,
1280+
redemption_key: [u8; redeem::PRIVATEKEY_SIZE], // hex
1281+
input: TxIn,
1282+
output: TxOut,
1283+
}
1284+
1285+
#[derive(Serialize, Deserialize, Debug)]
1286+
struct WalletRedeemOutput {
1287+
cbor_encoded_tx: Vec<u8>,
1288+
}
1289+
1290+
#[no_mangle]
1291+
pub extern "C" fn xwallet_redeem(
1292+
input_ptr: *const c_uchar,
1293+
input_sz: usize,
1294+
output_ptr: *mut c_uchar,
1295+
) -> i32 {
1296+
let data: WalletRedeemInput = input_json!(output_ptr, input_ptr, input_sz);
1297+
let mut txbuilder = txbuild::TxBuilder::new();
1298+
txbuilder.add_input(&data.input.convert(), data.output.value.0);
1299+
txbuilder.add_output_value(&data.output.convert());
1300+
let tx: tx::Tx = jrpc_try!(
1301+
output_ptr,
1302+
txbuilder.make_tx()
1303+
);
1304+
print!("Tx: {}", tx);
1305+
let redemption_key = jrpc_try!(
1306+
output_ptr,
1307+
redeem::PrivateKey::from_slice(&data.redemption_key)
1308+
);
1309+
print!("Key: {}", redemption_key);
1310+
let witness = jrpc_try!(
1311+
output_ptr,
1312+
create_redemption_witness(data.protocol_magic, &redemption_key, &tx.id())
1313+
);
1314+
let mut finalized = txbuild::TxFinalized::new(tx);
1315+
jrpc_try!(
1316+
output_ptr,
1317+
finalized.add_witness(witness)
1318+
);
1319+
let txaux: tx::TxAux = jrpc_try!(
1320+
output_ptr,
1321+
finalized.make_txaux()
1322+
);
1323+
let cbor = jrpc_try!(output_ptr, cbor!(&txaux));
1324+
jrpc_ok!(output_ptr, WalletRedeemOutput {
1325+
cbor_encoded_tx: cbor
1326+
})
1327+
}
1328+
1329+
fn create_redemption_witness(
1330+
protocol_magic: cardano::config::ProtocolMagic,
1331+
key: &redeem::PrivateKey,
1332+
txid: &tx::TxId,
1333+
) -> redeem::Result<tx::TxInWitness> {
1334+
// TODO: actual implementation
1335+
let s32 = (0..64).map(|_| "f").collect::<String>();
1336+
let s64 = (0..128).map(|_| "f").collect::<String>();
1337+
let pk = redeem::PublicKey::from_hex(&s32);
1338+
let sg = redeem::Signature::from_hex(&s64);
1339+
return pk.and_then(|k| sg.map(|s| (k, s)))
1340+
.map(|(k,s)| tx::TxInWitness::RedeemWitness(k, s));
1341+
}

0 commit comments

Comments
 (0)