public async Task <Transaction> SignTransactionAsync(SignatureRequest[] signatureRequests, Transaction transaction, KeyPath changePath = null, CancellationToken cancellation = default(CancellationToken)) { if (signatureRequests.Length == 0) { throw new ArgumentException("No signatureRequests is passed", "signatureRequests"); } var segwitCoins = signatureRequests.Where(s => s.InputCoin.GetHashVersion() == HashVersion.Witness).Count(); if (segwitCoins != signatureRequests.Count() && segwitCoins != 0) { throw new ArgumentException("Mixing segwit input with non segwit input is not supported", "signatureRequests"); } var segwitMode = segwitCoins != 0; Dictionary <OutPoint, SignatureRequest> requests = signatureRequests .ToDictionaryUnique(o => o.InputCoin.Outpoint); transaction = transaction.Clone(); Dictionary <OutPoint, IndexedTxIn> inputsByOutpoint = transaction.Inputs.AsIndexedInputs().ToDictionary(i => i.PrevOut); Dictionary <OutPoint, ICoin> coinsByOutpoint = requests.ToDictionary(o => o.Key, o => o.Value.InputCoin); List <Task <TrustedInput> > trustedInputsAsync = new List <Task <TrustedInput> >(); if (!segwitMode) { foreach (var sigRequest in signatureRequests) { trustedInputsAsync.Add(GetTrustedInputAsync(sigRequest.InputTransaction, (int)sigRequest.InputCoin.Outpoint.N, cancellation)); } } var noPubKeyRequests = signatureRequests.Where(r => r.PubKey == null).ToArray(); List <Task <GetWalletPubKeyResponse> > getPubKeys = new List <Task <GetWalletPubKeyResponse> >(); foreach (var previousReq in noPubKeyRequests) { getPubKeys.Add(GetWalletPubKeyAsync(previousReq.KeyPath, cancellation: cancellation)); } await Task.WhenAll(getPubKeys).ConfigureAwait(false); await Task.WhenAll(trustedInputsAsync).ConfigureAwait(false); for (int i = 0; i < noPubKeyRequests.Length; i++) { noPubKeyRequests[i].PubKey = getPubKeys[i].Result.UncompressedPublicKey.Compress(); } var trustedInputs = trustedInputsAsync.Select(t => t.Result).ToDictionaryUnique(i => i.OutPoint); List <byte[]> apdus = new List <byte[]>(); InputStartType inputStartType = segwitMode ? InputStartType.NewSegwit : InputStartType.New; bool segwitParsedOnce = false; for (int i = 0; i < signatureRequests.Length; i++) { var sigRequest = signatureRequests[i]; var input = inputsByOutpoint[sigRequest.InputCoin.Outpoint]; apdus.AddRange(UntrustedHashTransactionInputStart(inputStartType, input, trustedInputs, coinsByOutpoint, segwitMode, segwitParsedOnce)); inputStartType = InputStartType.Continue; if (!segwitMode || !segwitParsedOnce) { apdus.AddRange(UntrustedHashTransactionInputFinalizeFull(changePath, transaction.Outputs)); } changePath = null; //do not resubmit the changepath if (segwitMode && !segwitParsedOnce) { segwitParsedOnce = true; i--; //pass once more continue; } apdus.Add(UntrustedHashSign(sigRequest.KeyPath, null, transaction.LockTime, SigHash.All)); } var responses = await ExchangeAsync(apdus.ToArray(), cancellation).ConfigureAwait(false); foreach (var response in responses) { if (response.Response.Length > 10) //Probably a signature { response.Response[0] = 0x30; } } var signatures = responses.Where(p => TransactionSignature.IsValid(p.Response)).Select(p => new TransactionSignature(p.Response)).ToArray(); if (signatureRequests.Length != signatures.Length) { throw new LedgerWalletException("failed to sign some inputs"); } int sigIndex = 0; TransactionBuilder builder = new TransactionBuilder(); foreach (var sigRequest in signatureRequests) { var input = inputsByOutpoint[sigRequest.InputCoin.Outpoint]; if (input == null) { continue; } builder.AddCoins(sigRequest.InputCoin); builder.AddKnownSignature(sigRequest.PubKey, signatures[sigIndex]); sigIndex++; } builder.SignTransactionInPlace(transaction); sigIndex = 0; foreach (var sigRequest in signatureRequests) { var input = inputsByOutpoint[sigRequest.InputCoin.Outpoint]; if (input == null) { continue; } sigRequest.Signature = signatures[sigIndex]; if (!sigRequest.PubKey.Verify(transaction.GetSignatureHash(sigRequest.InputCoin, sigRequest.Signature.SigHash), sigRequest.Signature.Signature)) { foreach (var sigRequest2 in signatureRequests) { sigRequest2.Signature = null; } return(null); } sigIndex++; } return(transaction); }
public byte[][] UntrustedHashTransactionInputStart( InputStartType startType, IndexedTxIn txIn, Dictionary <OutPoint, TrustedInput> trustedInputs, Dictionary <OutPoint, ICoin> coins, bool segwitMode, bool segwitParsedOnce) { List <byte[]> apdus = new List <byte[]>(); trustedInputs = trustedInputs ?? new Dictionary <OutPoint, TrustedInput>(); // Start building a fake transaction with the passed inputs MemoryStream data = new MemoryStream(); BufferUtils.WriteBuffer(data, txIn.Transaction.Version); if (segwitMode && segwitParsedOnce) { VarintUtils.write(data, 1); } else { VarintUtils.write(data, txIn.Transaction.Inputs.Count); } apdus.Add(CreateAPDU(LedgerWalletConstants.LedgerWallet_CLA, LedgerWalletConstants.LedgerWallet_INS_HASH_INPUT_START, (byte)0x00, (byte)startType, data.ToArray())); // Loop for each input long currentIndex = 0; foreach (var input in txIn.Transaction.Inputs) { if (segwitMode && segwitParsedOnce && currentIndex != txIn.Index) { currentIndex++; continue; } byte[] script = new byte[0]; if (currentIndex == txIn.Index || segwitMode && !segwitParsedOnce) { script = coins[input.PrevOut].GetScriptCode().ToBytes(); } data = new MemoryStream(); if (segwitMode) { data.WriteByte(0x02); BufferUtils.WriteBuffer(data, input.PrevOut); BufferUtils.WriteBuffer(data, Utils.ToBytes((ulong)coins[input.PrevOut].TxOut.Value.Satoshi, true)); } else { var trustedInput = trustedInputs[input.PrevOut]; if (trustedInput != null) { data.WriteByte(0x01); var b = trustedInput.ToBytes(); // untrusted inputs have constant length data.WriteByte((byte)b.Length); BufferUtils.WriteBuffer(data, b); } else { data.WriteByte(0x00); BufferUtils.WriteBuffer(data, input.PrevOut); } } VarintUtils.write(data, script.Length); apdus.Add(CreateAPDU(LedgerWalletConstants.LedgerWallet_CLA, LedgerWalletConstants.LedgerWallet_INS_HASH_INPUT_START, (byte)0x80, (byte)0x00, data.ToArray())); data = new MemoryStream(); BufferUtils.WriteBuffer(data, script); BufferUtils.WriteBuffer(data, input.Sequence); apdus.AddRange(CreateAPDUSplit(LedgerWalletConstants.LedgerWallet_CLA, LedgerWalletConstants.LedgerWallet_INS_HASH_INPUT_START, (byte)0x80, (byte)0x00, data.ToArray())); currentIndex++; } return(apdus.ToArray()); }